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 ]
when:
event: push
branch: [master]
branch: [ master ]
build_release:
image: docker

8
Dockerfile

@ -1,7 +1,7 @@
#
# 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
RUN apt-get update \
@ -45,7 +45,7 @@ RUN cp -a /tmp/node_modules /src/Squidex/ \
# Test Backend
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.Read.Tests/Squidex.Domain.Apps.Read.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
#
FROM microsoft/aspnetcore:2.0.0-jessie
FROM microsoft/aspnetcore:2.0.3-jessie
# Default AspNetCore directory
WORKDIR /app
@ -66,5 +66,7 @@ WORKDIR /app
COPY --from=builder /out/ .
EXPOSE 80
EXPOSE 33333
EXPOSE 40000
ENTRYPOINT ["dotnet", "Squidex.dll"]

1
NuGet.Config

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

33
Squidex.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2002
VisualStudioVersion = 15.0.27004.2009
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}"
EndProject
@ -34,10 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Rabb
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.GoogleCloud", "src\Squidex.Infrastructure.GoogleCloud\Squidex.Infrastructure.GoogleCloud.csproj", "{945871B1-77B8-43FB-B53C-27CF385AB756}"
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}"
EndProject
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Core.Operations", "src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj", "{6B3F75B6-5888-468E-BA4F-4FC725DAEF31}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "tests\Benchmarks\Benchmarks.csproj", "{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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|x86.ActiveCfg = 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.Build.0 = 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|x86.ActiveCfg = Release|Any CPU
{6B3F75B6-5888-468E-BA4F-4FC725DAEF31}.Release|x86.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -335,7 +333,6 @@ Global
{D7166C56-178A-4457-B56A-C615C7450DEE} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{C1E5BBB6-6B6A-4DE5-B19D-0538304DE343} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{945871B1-77B8-43FB-B53C-27CF385AB756} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{D48A03DF-BCD3-4667-8747-2F251347E2B6} = {B56EBCEC-9C50-46A7-848C-65502DE69C5C}
{B51126A8-0D75-4A79-867D-10724EC6AC84} = {94207AA6-4923-4183-A558-E0F8196B8CA3}
{5E75AB7D-6F01-4313-AFF1-7F7128FFD71F} = {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_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_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/@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/@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/@EntryIndexRemoved">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/=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>

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.
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
@ -13,8 +14,8 @@ namespace Squidex.Domain.Apps.Core.Apps
public sealed class AppClient
{
private readonly string secret;
private string name;
private AppClientPermission permission;
private readonly string name;
private readonly AppClientPermission permission;
public string Name
{
@ -42,18 +43,20 @@ namespace Squidex.Domain.Apps.Core.Apps
this.permission = permission;
}
public void Update(AppClientPermission newPermission)
[Pure]
public AppClient Update(AppClientPermission 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));
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.
// ==========================================================================
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
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.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));
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));
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.
// ==========================================================================
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
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.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));
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.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
@ -26,18 +28,11 @@ namespace Squidex.Domain.Apps.Core.Apps.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 clients = new AppClients();
foreach (var client in json)
{
clients.Add(client.Key, client.Value.ToClient());
}
return clients;
return new AppClients(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToClient()));
}
}
}

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

@ -6,7 +6,9 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
@ -26,18 +28,11 @@ namespace Squidex.Domain.Apps.Core.Apps.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 contributors = new AppContributors();
foreach (var contributor in json)
{
contributors.Assign(contributor.Key, contributor.Value);
}
return contributors;
return new AppContributors(json.ToImmutableDictionary());
}
}
}

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)
{
result.MakeMaster(Master);
result = result.MakeMaster(Master);
}
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);
}
protected override LanguagesConfig ReadValue(JsonReader reader, JsonSerializer serializer)
protected override LanguagesConfig ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
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.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq;
using Squidex.Infrastructure;
#pragma warning disable IDE0016 // Use 'throw' expression
namespace Squidex.Domain.Apps.Core.Apps
{
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
{
get { return state.Master; }
get { return master; }
}
IFieldPartitionItem IFieldPartitioning.Master
{
get { return state.Master; }
get { return master; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return state.Languages.Values.GetEnumerator();
return languages.Values.GetEnumerator();
}
IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator()
{
return state.Languages.Values.GetEnumerator();
return languages.Values.GetEnumerator();
}
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));
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));
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));
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));
var newLanguages =
state.Languages.Values.Where(x => x.Language != language)
languages.Values.Where(x => x.Language != language)
.Select(config =>
{
return new LanguageConfig(
@ -97,26 +122,26 @@ namespace Squidex.Domain.Apps.Core.Apps
.ToImmutableDictionary(x => x.Language);
var newMaster =
state.Master.Language != language ?
state.Master :
Master.Language != language ?
Master :
newLanguages.Values.FirstOrDefault();
state = new State(newLanguages, newMaster);
return new LanguagesConfig(newLanguages, newMaster);
}
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)
{
return state.Languages.TryGetValue(language, out config);
return languages.TryGetValue(language, out config);
}
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;
@ -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()
{
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.Generic;
using System.Collections.Immutable;
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]
{
@ -35,11 +36,16 @@ namespace Squidex.Domain.Apps.Core
get { return inner.Count; }
}
protected Dictionary<TKey, TValue> Inner
protected ImmutableDictionary<TKey, TValue> Inner
{
get { return inner; }
}
protected DictionaryWrapper(ImmutableDictionary<TKey, TValue> inner)
{
this.inner = inner;
}
public bool ContainsKey(TKey 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))]
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)
{

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
{
public interface IRuleActionVisitor<T>
public interface IRuleActionVisitor<out T>
{
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
{
public interface IRuleTriggerVisitor<T>
public interface IRuleTriggerVisitor<out T>
{
T Visit(ContentChangedTrigger trigger);
}

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

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
@ -13,14 +14,14 @@ namespace Squidex.Domain.Apps.Core.Rules.Json
{
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.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules
{
public sealed class Rule
public sealed class Rule : Cloneable<Rule>
{
private RuleTrigger trigger;
private RuleAction action;
@ -41,17 +42,26 @@ namespace Squidex.Domain.Apps.Core.Rules
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));
@ -60,10 +70,14 @@ namespace Squidex.Domain.Apps.Core.Rules
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));
@ -72,7 +86,10 @@ namespace Squidex.Domain.Apps.Core.Rules
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.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules
{
public abstract class RuleAction
public abstract class RuleAction : Freezable
{
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.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules
{
public abstract class RuleTrigger
public abstract class RuleTrigger : Freezable
{
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.
// ==========================================================================
using System.Collections.Generic;
using System.Collections.Immutable;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Triggers
@ -14,7 +14,21 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers
[TypeName(nameof(ContentChangedTrigger))]
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)
{

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

@ -13,13 +13,45 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(AssetsField))]
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)
{
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))]
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)
{
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))]
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)
{
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.
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class Field
public abstract class Field : Cloneable<Field>
{
private readonly long fieldId;
private readonly Partitioning partitioning;
@ -63,32 +64,52 @@ namespace Squidex.Domain.Apps.Core.Schemas
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);
}

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 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 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 readonly TypeNameRegistry typeNameRegistry;
private readonly Dictionary<Type, Registered> fieldsByPropertyType = new Dictionary<Type, Registered>();
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);
}
}
private readonly Dictionary<Type, FactoryFunction> fieldsByPropertyType = new Dictionary<Type, FactoryFunction>();
public FieldRegistry(TypeNameRegistry typeNameRegistry)
{
@ -47,69 +25,39 @@ namespace Squidex.Domain.Apps.Core.Schemas
this.typeNameRegistry = typeNameRegistry;
Add<BooleanFieldProperties>(
(id, name, partitioning, properties) =>
new BooleanField(id, name, partitioning, (BooleanFieldProperties)properties));
Add<NumberFieldProperties>(
(id, name, partitioning, properties) =>
new NumberField(id, name, partitioning, (NumberFieldProperties)properties));
Add<StringFieldProperties>(
(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));
RegisterField<AssetsFieldProperties>();
RegisterField<BooleanFieldProperties>();
RegisterField<DateTimeFieldProperties>();
RegisterField<GeolocationFieldProperties>();
RegisterField<JsonFieldProperties>();
RegisterField<NumberFieldProperties>();
RegisterField<ReferencesFieldProperties>();
RegisterField<StringFieldProperties>();
RegisterField<TagsFieldProperties>();
typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "DateTime");
typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "References");
}
private void Add<TFieldProperties>(FactoryFunction fieldFactory)
private void RegisterField<T>()
{
Guard.NotNull(fieldFactory, nameof(fieldFactory));
typeNameRegistry.Map(typeof(TFieldProperties));
var registered = new Registered(fieldFactory, typeof(TFieldProperties));
typeNameRegistry.Map(typeof(T));
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)
{
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.");
}
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
// ==========================================================================
// Copyright (c) Squidex Group
@ -7,7 +7,9 @@
// ==========================================================================
using System;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
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));
this.properties = properties;
this.properties.Freeze();
}
public override void Update(FieldProperties newProperties)
[Pure]
public override Field Update(FieldProperties newProperties)
{
var typedProperties = ValidateProperties(newProperties);
properties = typedProperties;
return Clone<Field<T>>(clone =>
{
clone.properties = typedProperties;
clone.properties.Freeze();
});
}
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))]
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)
{
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
{
private static readonly Field[] Empty = new Field[0];
[JsonProperty]
public string Name { get; set; }
@ -54,46 +56,40 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public Schema ToSchema(FieldRegistry fieldRegistry)
{
var schema = new Schema(Name);
Field[] fields = Empty;
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 field = fieldRegistry.CreateField(fieldModel.Id, fieldModel.Name, parititonKey, fieldModel.Properties);
if (fieldModel.IsDisabled)
{
field.Disable();
field = field.Disable();
}
if (fieldModel.IsLocked)
{
field.Lock();
field = field.Lock();
}
if (fieldModel.IsHidden)
{
field.Hide();
field = field.Hide();
}
schema.AddField(field);
fields[i] = field;
}
}
if (IsPublished)
{
schema.Publish();
}
if (Properties != null)
{
schema.Update(Properties);
}
return schema;
return new Schema(Name, fields, Properties, IsPublished);
}
}
}

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

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
@ -28,7 +29,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
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);
}

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

@ -17,5 +17,10 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
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.
// ==========================================================================
using Squidex.Infrastructure;
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.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
@ -13,19 +14,90 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(NumberField))]
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)
{
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))]
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)
{
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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class Schema
public sealed class Schema : Cloneable<Schema>
{
private readonly string name;
private readonly List<Field> fieldsOrdered = new List<Field>();
private readonly Dictionary<long, Field> fieldsById = new Dictionary<long, Field>();
private readonly Dictionary<string, Field> fieldsByName = new Dictionary<string, Field>();
private SchemaProperties properties = new SchemaProperties();
private ImmutableArray<Field> fieldsOrdered = ImmutableArray<Field>.Empty;
private ImmutableDictionary<long, Field> fieldsById;
private ImmutableDictionary<string, Field> fieldsByName;
private SchemaProperties properties;
private bool isPublished;
public string Name
@ -39,12 +41,42 @@ namespace Squidex.Domain.Apps.Core.Schemas
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
{
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
@ -52,69 +84,177 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return properties; }
}
public void Publish()
public Schema(string name, SchemaProperties properties = null)
{
isPublished = true;
}
Guard.NotNullOrEmpty(name, nameof(name));
public void Unpublish()
{
isPublished = false;
this.name = name;
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.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));
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);
fieldsByName.Remove(field.Name);
fieldsOrdered.Remove(field);
return Clone(clone =>
{
clone.fieldsOrdered = fieldsOrdered.Remove(field);
});
}
public void ReorderFields(List<long> ids)
[Pure]
public Schema ReorderFields(List<long> 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));
}
var fields = fieldsOrdered.ToList();
fieldsOrdered.Clear();
fieldsOrdered.AddRange(fields.OrderBy(f => ids.IndexOf(f.Id)));
return Clone(clone =>
{
clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToImmutableArray();
});
}
public void AddField(Field field)
[Pure]
public Schema AddField(Field 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));
}
fieldsById.Add(field.Id, field);
fieldsByName.Add(field.Name, field);
fieldsOrdered.Add(field);
return Clone(clone =>
{
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.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
@ -13,23 +14,120 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(StringField))]
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)
{
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))]
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)
{
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)
{
IEnumerable<Guid> result = null;
IEnumerable<Guid> result;
try
{
result = value?.ToObject<List<Guid>>();
@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
private static IEnumerable<Guid> Visit(ReferencesField field, JToken value)
{
IEnumerable<Guid> result = null;
IEnumerable<Guid> result;
try
{
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.Http;
namespace Squidex.Domain.Apps.Core.HandleRules.ActionHandlers
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
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)
{
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)
{
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;
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="Microsoft.OData.Core" Version="7.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NJsonSchema" Version="9.8.3" />
<PackageReference Include="NodaTime" Version="2.2.1" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" />
<PackageReference Include="NJsonSchema" Version="9.10.9" />
<PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.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 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))
{
client.Rename(@event.Name);
}
return clients.Rename(@event.Id, @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))
{
client.Update(@event.Permission);
}
return clients.Update(@event.Id, @event.Permission);
}
public static void Apply(this LanguagesConfig languagesConfig, AppLanguageUpdated @event)
public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageUpdated @event)
{
var fallback = @event.Fallback;
@ -70,12 +64,14 @@ namespace Squidex.Domain.Apps.Events.Apps.Utils
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)
{
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);
}
public static void Apply(this Rule rule, RuleUpdated @event)
public static Rule Apply(this Rule rule, RuleUpdated @event)
{
if (@event.Trigger != null)
{
rule.Update(@event.Trigger);
return rule.Update(@event.Trigger);
}
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)
{
schema.Update(@event.Properties);
schema = schema.Update(@event.Properties);
}
if (@event.Fields != null)
@ -38,20 +38,20 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
if (eventField.IsHidden)
{
field.Hide();
field = field.Hide();
}
if (eventField.IsDisabled)
{
field.Disable();
field = field.Disable();
}
if (eventField.IsLocked)
{
field.Lock();
field = field.Lock();
}
schema.AddField(field);
schema = schema.AddField(field);
fieldId++;
}
@ -60,91 +60,74 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
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 =
string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ?
Partitioning.Language :
Partitioning.Invariant;
var fieldId = @event.FieldId.Id;
var field = registry.CreateField(fieldId, @event.Name, partitioning, @event.Properties);
var field = registry.CreateField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties);
schema.DeleteField(fieldId);
schema.AddField(field);
schema = schema.DeleteField(@event.FieldId.Id);
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))
{
field.Update(@event.Properties);
}
return schema.UpdateField(@event.FieldId.Id, @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))
{
field.Lock();
}
return schema.LockField(@event.FieldId.Id);
}
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))
{
field.Hide();
}
return schema.HideField(@event.FieldId.Id);
}
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))
{
field.Show();
}
return schema.ShowField(@event.FieldId.Id);
}
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))
{
field.Disable();
}
return schema.DisableField(@event.FieldId.Id);
}
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))
{
field.Enable();
}
return schema.EnableField(@event.FieldId.Id);
}
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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NodaTime" Version="2.2.1" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" />
<PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</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
{
public sealed class MongoAssetEntity : MongoEntity, IAssetEntity
public sealed class MongoAssetEntity :
MongoEntity,
IAssetEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy
{
[BsonRequired]
[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
{
public sealed class MongoContentEntity : IContentEntity, IMongoEntity
public sealed class MongoContentEntity :
IContentEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy
{
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.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
@ -29,7 +28,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
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(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
this.database = database;
this.schemas = schemas;
this.appProvider = appProvider;
}
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;
}
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)
{

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)
{
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 =>
{
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
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);

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
{
public sealed class MongoHistoryEventEntity : MongoEntity, IAppRefEntity, IEntityWithCreatedBy
public sealed class MongoHistoryEventEntity : MongoEntity,
IEntityWithAppRef,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy
{
[BsonRequired]
[BsonElement]
@ -40,7 +43,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
[BsonElement]
public Dictionary<string, string> Parameters { get; set; }
RefToken IEntityWithCreatedBy.CreatedBy
RefToken IUpdateableEntityWithCreatedBy.CreatedBy
{
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)
{
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.AppId = appEvent.AppId.Id;
entity.Version = @event.Headers.EventStreamNumber();
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.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
{
internal sealed class ParsedHistoryEvent : IHistoryEventEntity
@ -22,26 +24,29 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
public Guid 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

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 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);
updater(entity);
var entity = EntityMapper.Create(@event, headers, updater);
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()
{
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()
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new()
{
var entity =
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);
}
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 =
await collection.Find(t => t.Id == headers.AggregateId())
@ -76,11 +65,9 @@ namespace Squidex.Domain.Apps.Read.MongoDb
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);
updater(entity);
entity.Update(@event, headers, updater);
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]
public Instant? NextAttempt { get; set; }
[BsonRequired]
[BsonElement]
public bool IsSending { get; set; }
[BsonRequired]
[BsonElement]
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)
{
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.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero }));
}
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)
@ -81,18 +81,12 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
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)
{
return Collection.UpdateOneAsync(x => x.Id == jobId,
Update.Set(x => x.Result, result)
.Set(x => x.LastDump, dump)
.Set(x => x.JobResult, jobResult)
.Set(x => x.IsSending, false)
.Set(x => x.NextAttempt, nextAttempt)
.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>
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />
<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="System.ValueTuple" Version="4.4.0" />
</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
// ==========================================================================
// Copyright (c) Squidex Group
// 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 app;
return entity.LanguagesConfig.ToResolver();
}
}
}

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

@ -6,13 +6,14 @@
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Domain.Apps.Read.Apps
{
public interface IAppEntity : IEntity, IEntityWithVersion
{
string Etag { get; }
string Name { get; }
string PlanId { get; }
@ -24,7 +25,5 @@ namespace Squidex.Domain.Apps.Read.Apps
AppContributors Contributors { 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
{
public interface IAssetEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
public interface IAssetEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{
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.");
AddEventMessage<ContentStatusChanged>(
"change status of content item to {[Status]}.");
"changed status of content item to {[Status]}.");
}
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event)
{
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.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
@ -30,23 +29,23 @@ namespace Squidex.Domain.Apps.Read.Contents
public sealed class ContentQueryService : IContentQueryService
{
private readonly IContentRepository contentRepository;
private readonly ISchemaProvider schemas;
private readonly IAppProvider appProvider;
private readonly IScriptEngine scriptEngine;
private readonly EdmModelBuilder modelBuilder;
public ContentQueryService(
IContentRepository contentRepository,
ISchemaProvider schemas,
IAppProvider appProvider,
IScriptEngine scriptEngine,
EdmModelBuilder modelBuilder)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
this.contentRepository = contentRepository;
this.schemas = schemas;
this.appProvider = appProvider;
this.scriptEngine = scriptEngine;
this.modelBuilder = modelBuilder;
}
@ -156,12 +155,12 @@ namespace Squidex.Domain.Apps.Read.Contents
if (Guid.TryParse(schemaIdOrName, out var id))
{
schema = await schemas.FindSchemaByIdAsync(id);
schema = await appProvider.GetSchemaAsync(app.Name, id);
}
if (schema == null)
{
schema = await schemas.FindSchemaByNameAsync(app.Id, schemaIdOrName);
schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName);
}
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);
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver);
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver());
});
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.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
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 readonly IContentQueryService contentQuery;
private readonly IGraphQLUrlGenerator urlGenerator;
private readonly IAssetRepository assetRepository;
private readonly ISchemaRepository schemaRepository;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^(schema-)|(apps-)"; }
}
private readonly IAppProvider appProvider;
public CachingGraphQLService(IMemoryCache cache,
IAppProvider appProvider,
IAssetRepository assetRepository,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator,
ISchemaRepository schemaRepository)
IGraphQLUrlGenerator urlGenerator)
: base(cache)
{
Guard.NotNull(schemaRepository, nameof(schemaRepository));
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentQuery, nameof(urlGenerator));
Guard.NotNull(contentQuery, nameof(contentQuery));
this.appProvider = appProvider;
this.assetRepository = assetRepository;
this.contentQuery = contentQuery;
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)
@ -90,13 +61,13 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
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);
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);
@ -106,9 +77,9 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
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;
partitionResolver = app.PartitionResolver;
partitionResolver = app.PartitionResolver();
assetType = new AssetGraphType(this);
assetListType = new ListGraphType(new NonNullGraphType(assetType));

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

@ -6,6 +6,8 @@
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public class GraphQLQuery
@ -16,6 +18,6 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
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
{
public interface IContentEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
public interface IContentEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{
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.
// ==========================================================================
using System;
using Squidex.Domain.Apps.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 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();
@ -26,60 +26,62 @@ namespace Squidex.Domain.Apps.Read.MongoDb
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);
SetLastModified(headers, entity);
SetLastModifiedBy(@event, entity);
updater?.Invoke(entity);
return entity;
}
private static void SetId(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetId(EnvelopeHeaders headers, IEntity entity)
{
entity.Id = headers.AggregateId();
}
private static void SetCreated(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetCreated(EnvelopeHeaders headers, IEntity entity)
{
entity.Created = headers.Timestamp();
}
private static void SetLastModified(EnvelopeHeaders headers, IMongoEntity entity)
private static void SetLastModified(EnvelopeHeaders headers, IEntity entity)
{
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();
}
}
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;
}
}
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;
}
}
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;
}

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
{
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
{
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
{
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
// ==========================================================================
// Copyright (c) Squidex Group
@ -10,8 +10,8 @@ using System;
namespace Squidex.Domain.Apps.Read
{
public interface IAppRefEntity : IEntity
public interface IUpdateableEntityWithAppRef
{
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
// ==========================================================================
// Copyright (c) Squidex Group
// 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
{
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 MarkSendingAsync(Guid jobId);
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));

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

@ -7,11 +7,13 @@
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
@ -22,10 +24,10 @@ namespace Squidex.Domain.Apps.Read.Rules
public sealed class RuleDequeuer : DisposableObjectBase, IExternalSystem
{
private readonly ActionBlock<IRuleEventEntity> requestBlock;
private readonly TransformBlock<IRuleEventEntity, IRuleEventEntity> blockBlock;
private readonly IRuleEventRepository ruleEventRepository;
private readonly RuleService ruleService;
private readonly CompletionTimer timer;
private readonly ConcurrentDictionary<Guid, bool> executing = new ConcurrentDictionary<Guid, bool>();
private readonly IClock clock;
private readonly ISemanticLog log;
@ -44,15 +46,9 @@ namespace Squidex.Domain.Apps.Read.Rules
this.log = log;
requestBlock =
new ActionBlock<IRuleEventEntity>(MakeRequestAsync,
new ActionBlock<IRuleEventEntity>(HandleAsync,
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);
}
@ -62,7 +58,7 @@ namespace Squidex.Domain.Apps.Read.Rules
{
timer.StopAsync().Wait();
blockBlock.Complete();
requestBlock.Complete();
requestBlock.Completion.Wait();
}
}
@ -82,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Rules
{
var now = clock.GetCurrentInstant();
await ruleEventRepository.QueryPendingAsync(now, blockBlock.SendAsync, cancellationToken);
await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync, cancellationToken);
}
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 @event;
return;
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "BlockWebhookEvent")
.WriteProperty("status", "Failed"));
throw;
}
}
private async Task MakeRequestAsync(IRuleEventEntity @event)
{
try
{
var job = @event.Job;
var response = await ruleService.InvokeAsync(job.ActionName, job.ActionData);
Instant? nextCall = null;
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;
var jobInvoke = ComputeJobInvoke(response.Result, @event, job);
var jobResult = ComputeJobResult(response.Result, jobInvoke);
if (response.Result != RuleResult.Success && !nextCall.HasValue)
{
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);
await ruleEventRepository.MarkSentAsync(@event.Id, response.Dump, response.Result, jobResult, response.Elapsed, jobInvoke);
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "SendWebhookEvent")
.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
{
private readonly IRuleEventRepository ruleEventRepository;
private readonly IRuleRepository ruleRepository;
private readonly IAppProvider appProvider;
private readonly RuleService ruleService;
public string Name
@ -33,17 +33,18 @@ namespace Squidex.Domain.Apps.Read.Rules
}
public RuleEnqueuer(
IRuleEventRepository ruleEventRepository,
IRuleRepository ruleRepository,
IRuleEventRepository ruleEventRepository, IAppProvider appProvider,
RuleService ruleService)
{
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository));
Guard.NotNull(ruleRepository, nameof(ruleRepository));
Guard.NotNull(ruleService, nameof(ruleService));
Guard.NotNull(appProvider, nameof(appProvider));
this.ruleEventRepository = ruleEventRepository;
this.ruleRepository = ruleRepository;
this.ruleService = ruleService;
this.appProvider = appProvider;
}
public Task ClearAsync()
@ -55,11 +56,11 @@ namespace Squidex.Domain.Apps.Read.Rules
{
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)
{
var job = ruleService.CreateJob(ruleEntity.Rule, @event);
var job = ruleService.CreateJob(ruleEntity.RuleDef, @event);
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
{
public interface ISchemaEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
public interface ISchemaEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{
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