Browse Source

Merge branch 'master' into feature-asset-management

pull/65/head
Sebastian 9 years ago
parent
commit
bf99334f66
  1. 2
      Dockerfile.build
  2. 18
      Squidex.sln
  3. 30
      Squidex.sln.DotSettings
  4. BIN
      media/logo-white.png
  5. 112
      media/logo-white.svg
  6. 79
      src/Squidex.Core/ContentEnricher.cs
  7. 52
      src/Squidex.Core/ContentExtensions.cs
  8. 190
      src/Squidex.Core/ContentValidator.cs
  9. 26
      src/Squidex.Core/Contents/ContentData.cs
  10. 39
      src/Squidex.Core/FieldExtensions.cs
  11. 2
      src/Squidex.Core/Identity/SquidexClaimTypes.cs
  12. 5
      src/Squidex.Core/Schemas/BooleanField.cs
  13. 3
      src/Squidex.Core/Schemas/BooleanFieldEditor.cs
  14. 6
      src/Squidex.Core/Schemas/CloneableBase.cs
  15. 10
      src/Squidex.Core/Schemas/DateTimeField.cs
  16. 2
      src/Squidex.Core/Schemas/DateTimeFieldProperties.cs
  17. 74
      src/Squidex.Core/Schemas/Field.cs
  18. 2
      src/Squidex.Core/Schemas/FieldProperties.cs
  19. 5
      src/Squidex.Core/Schemas/FieldRegistry.cs
  20. 89
      src/Squidex.Core/Schemas/GeolocationField.cs
  21. 15
      src/Squidex.Core/Schemas/GeolocationFieldEditor.cs
  22. 44
      src/Squidex.Core/Schemas/GeolocationFieldProperties.cs
  23. 5
      src/Squidex.Core/Schemas/JsonField.cs
  24. 4
      src/Squidex.Core/Schemas/NamedElementPropertiesBase.cs
  25. 5
      src/Squidex.Core/Schemas/NumberField.cs
  26. 3
      src/Squidex.Core/Schemas/NumberFieldEditor.cs
  27. 167
      src/Squidex.Core/Schemas/Schema.cs
  28. 2
      src/Squidex.Core/Schemas/SchemaProperties.cs
  29. 5
      src/Squidex.Core/Schemas/StringField.cs
  30. 2
      src/Squidex.Core/Schemas/StringFieldEditor.cs
  31. 6
      src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs
  32. 6
      src/Squidex.Core/Schemas/Validators/IValidator.cs
  33. 28
      src/Squidex.Core/Schemas/Validators/PatternValidator.cs
  34. 7
      src/Squidex.Core/Schemas/Validators/RangeValidator.cs
  35. 6
      src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs
  36. 6
      src/Squidex.Core/Schemas/Validators/RequiredValidator.cs
  37. 29
      src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs
  38. 2
      src/Squidex.Core/Squidex.Core.csproj
  39. 234
      src/Squidex.Events/.gitignore
  40. 2
      src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs
  41. 2
      src/Squidex.Events/SquidexEvent.cs
  42. 14
      src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
  43. 25
      src/Squidex.Infrastructure.MongoDb/InstantSerializer.cs
  44. 15
      src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs
  45. 25
      src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs
  46. 2
      src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  47. 2
      src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs
  48. 4
      src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.csproj
  49. 59
      src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
  50. 17
      src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs
  51. 9
      src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectFactory.cs
  52. 11
      src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
  53. 7
      src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs
  54. 18
      src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs
  55. 18
      src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs
  56. 20
      src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs
  57. 4
      src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs
  58. 1
      src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs
  59. 2
      src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
  60. 3
      src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
  61. 3
      src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs
  62. 6
      src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs
  63. 6
      src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs
  64. 2
      src/Squidex.Infrastructure/CQRS/Events/DefaultMemoryEventNotifier.cs
  65. 29
      src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs
  66. 2
      src/Squidex.Infrastructure/CQRS/Events/Envelope.cs
  67. 14
      src/Squidex.Infrastructure/CQRS/Events/EnvelopeExtensions.cs
  68. 3
      src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs
  69. 3
      src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
  70. 10
      src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs
  71. 2
      src/Squidex.Infrastructure/CollectionExtensions.cs
  72. 2
      src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs
  73. 2
      src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs
  74. 4
      src/Squidex.Infrastructure/DisposableObjectBase.cs
  75. 12
      src/Squidex.Infrastructure/DomainObjectVersionException.cs
  76. 44
      src/Squidex.Infrastructure/Json/ConverterContractResolver.cs
  77. 33
      src/Squidex.Infrastructure/Json/JsonExtension.cs
  78. 14
      src/Squidex.Infrastructure/Language.cs
  79. 4
      src/Squidex.Infrastructure/Languages.cs
  80. 4
      src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs
  81. 2
      src/Squidex.Infrastructure/Security/OpenIdClaims.cs
  82. 4
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  83. 11
      src/Squidex.Infrastructure/Tasks/TaskHelper.cs
  84. 2
      src/Squidex.Infrastructure/Timers/CompletionTimer.cs
  85. 6
      src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs
  86. 1
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  87. 4
      src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
  88. 43
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
  89. 17
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  90. 27
      src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs
  91. 4
      src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs
  92. 1
      src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs
  93. 8
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs
  94. 15
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  95. 3
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  96. 6
      src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj
  97. 29
      src/Squidex.Read.MongoDb/Utils/EntityMapper.cs
  98. 2
      src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs
  99. 1
      src/Squidex.Read/Apps/AppHistoryEventsCreator.cs
  100. 2
      src/Squidex.Read/Apps/IAppEntity.cs

2
Dockerfile.build

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore-build
FROM microsoft/aspnetcore-build:1.1.1
# Install runtime dependencies

18
Squidex.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26020.0
VisualStudioVersion = 15.0.26228.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}"
EndProject
@ -152,16 +152,16 @@ Global
{D7166C56-178A-4457-B56A-C615C7450DEE}.Release|x86.ActiveCfg = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.ActiveCfg = Debug|x64
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.Build.0 = Debug|x64
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.ActiveCfg = Debug|x86
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.Build.0 = Debug|x86
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.ActiveCfg = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.Build.0 = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.ActiveCfg = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.Build.0 = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|Any CPU.Build.0 = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.ActiveCfg = Release|x64
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.Build.0 = Release|x64
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.ActiveCfg = Release|x86
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.Build.0 = Release|x86
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.ActiveCfg = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.Build.0 = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.ActiveCfg = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

30
Squidex.sln.DotSettings

@ -14,41 +14,27 @@
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ets/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_005Ftest_002Doutput_002A/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_005Ftest_002Doutput_002A/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=61F6BBCE_002DA080_002D4400_002DB194_002D70E2F5D2096E_002Fd_003A_005Ftest_002Doutput/@EntryIndexedValue"></s:String>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=61F6BBCE_002DA080_002D4400_002DB194_002D70E2F5D2096E_002Fd_003A_005Ftest_002Doutput/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=A2569FB7_002D99E7_002D48B4_002DB97F_002DE48B6F57E492_002Fd_003Awwwroot_002Fd_003Anode_005Fmodules/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexedValue"></s:String>
<s:Boolean x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertMethodToExpressionBody/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertPropertyToExpressionBody/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoProperty/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoPropertyWhenPossible/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=Html_002ETagNotResolved/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MissingHasOwnPropertyInForeach/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReturnTypeCanBeEnumerable_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FElsewhere/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TsResolvedFromInaccessibleModule/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedAutoPropertyAccessor_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMethodReturnValue_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedParameter_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=61F6BBCE_002DA080_002D4400_002DB194_002D70E2F5D2096E_002Fd_003A_005Ftest_002Doutput/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=61F6BBCE_002DA080_002D4400_002DB194_002D70E2F5D2096E_002Fd_003A_005Ftest_002Doutput/@EntryIndexedValue"></s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=A2569FB7_002D99E7_002D48B4_002DB97F_002DE48B6F57E492_002Fd_003Awwwroot_002Fd_003Anode_005Fmodules/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/TypeScriptInspections/Level/@EntryValue">TypeScript16</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Header/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Header"&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD;
$FILENAME$&#xD;
@ -99,9 +85,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/TypeScriptCodeStyle/ExplicitPublicModifier/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

BIN
media/logo-white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

112
media/logo-white.svg

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="494px"
height="111px"
viewBox="0 0 494 111"
enable-background="new 0 0 494 111"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="logo-white.svg"
inkscape:export-filename="D:\Squidex\media\logo-white.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><metadata
id="metadata45"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs43" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview41"
showgrid="false"
inkscape:zoom="0.58299595"
inkscape:cx="247"
inkscape:cy="55.5"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><path
fill="#525873"
d="M90.902,65.898h15.952c1.21,4.842,4.841,8.143,14.632,8.143c6.931,0,10.232-1.65,10.232-4.951 c0-4.07-4.731-4.4-13.862-6.602c-17.163-4.07-24.204-7.92-24.204-18.371c0-12.764,10.562-20.135,26.514-20.135 c16.832,0,25.083,8.143,27.174,19.363h-15.952c-1.21-3.85-4.511-6.16-11.772-6.16c-6.271,0-10.122,1.76-10.122,5.061 c0,2.859,2.75,3.41,11.552,5.391C138.318,51.598,148,54.898,148,67c0,13.531-11.992,20.133-26.954,20.133 C104.874,87.133,93.102,79.211,90.902,65.898z"
id="path3"
style="fill:#ffffff" /><path
fill="#525873"
d="M213.235,25.303v75.439h-15.731v-23.07c-4.181,5.721-10.562,9.461-19.033,9.461 c-15.842,0-27.614-12.982-27.614-31.574c0-18.703,11.992-31.576,27.503-31.576c8.582,0,14.963,3.961,19.144,9.793v-8.473H213.235z M197.504,55.559c0-9.902-6.711-16.613-15.513-16.613c-8.801,0-15.292,6.711-15.292,16.613c0,10.01,6.601,16.611,15.292,16.611 C190.793,72.17,197.504,65.568,197.504,55.559z"
id="path5"
style="fill:#ffffff" /><path
fill="#525873"
d="M274.281,25.303v60.51h-15.732v-8.361c-4.4,6.16-10.342,9.682-18.703,9.682 c-13.312,0-21.562-9.131-21.562-23.873V25.303h15.732v34.545c0,7.922,2.971,12.322,11.111,12.322c8.691,0,13.422-5.17,13.422-15.072 V25.303H274.281z"
id="path7"
style="fill:#ffffff" /><path
fill="#525873"
d="M279.767,42.927H295.5v42.886h-15.732V42.927z"
id="path9"
style="fill:#ffffff" /><path
fill="#525873"
d="M363.037,8.506v77.307h-15.733v-8.141c-4.18,5.721-10.561,9.461-19.031,9.461 c-15.732,0-27.615-12.982-27.615-31.574c0-18.703,11.992-31.576,27.504-31.576c8.582,0,14.963,3.961,19.143,9.793V8.506H363.037z M347.304,55.559c0-9.902-6.711-16.613-15.512-16.613s-15.291,6.711-15.291,16.613c0,10.01,6.6,16.611,15.291,16.611 C340.593,72.17,347.304,65.568,347.304,55.559z"
id="path11"
style="fill:#ffffff" /><path
fill="#525873"
d="M430.254,58.309h-51.818c0.881,12.541,9.791,20.133,22.004,20.133c8.361,0,15.291-3.521,18.922-10.562h9.461 c-5.061,12.873-16.061,19.143-28.713,19.143c-17.934,0-31.025-12.762-31.025-31.244c0-18.373,12.873-31.684,30.916-31.684 C418.812,24.094,431.023,38.395,430.254,58.309z M378.877,49.727h41.475c-1.98-10.121-9.461-17.053-20.352-17.053 C388.887,32.674,380.967,39.826,378.877,49.727z"
id="path13"
style="fill:#ffffff" /><path
fill="#525873"
d="M469.848,85.812l-16.723-24.094l-16.502,24.094h-11.002l22.004-31.135l-20.793-29.375h11.002l15.512,22.555 l15.293-22.555h11.111l-20.904,29.484l22.004,31.025H469.848z"
id="path15"
style="fill:#ffffff" /><path
fill="#3389FF"
d="M290.666,30.575h2.198c1.455,0,2.636-1.181,2.636-2.636c0-1.456-1.181-2.637-2.636-2.637h-10.461 c-1.455,0-2.636,1.181-2.636,2.637c0,0.805,0.368,1.516,0.938,2l-0.018,0.013c0.649,0.542,1.071,1.347,1.071,2.259 c0,0.9-0.411,1.696-1.046,2.237l0.018,0.031c-0.583,0.484-0.963,1.205-0.963,2.022c0,1.455,1.181,2.636,2.636,2.636h8.23 c1.455,0,2.636-1.181,2.636-2.636c0-1.456-1.181-2.637-2.636-2.637h0.21c-0.911-0.003-1.649-0.742-1.649-1.654 C289.194,31.36,289.839,30.668,290.666,30.575z"
id="path17"
style="fill:#ffffff" /><g
id="g19"
style="fill:#ffffff"><g
id="g21"
style="fill:#ffffff"><path
fill="#3389FF"
d="M21.267,80.827c-0.473,0-0.971-0.031-1.494-0.096c-3.67-0.459-6.484-2.862-7.531-6.428 c-1.109-3.789,0.053-8.048,2.826-10.359c1.144-0.955,2.844-0.799,3.797,0.345c0.953,1.145,0.798,2.844-0.346,3.797 c-1.138,0.948-1.611,2.968-1.104,4.7c0.309,1.051,1.083,2.353,3.025,2.596c1.399,0.175,2.459-0.063,3.246-0.724 c1.435-1.209,1.869-3.684,1.922-4.596V53.077c0-1.489,1.207-2.695,2.695-2.695S31,51.588,31,53.077v17.051 c0,0.033,0,0.065-0.001,0.098c-0.02,0.558-0.297,5.538-3.803,8.525C26.031,79.742,24.107,80.827,21.267,80.827z"
id="path23"
style="fill:#ffffff" /></g><g
id="g25"
style="fill:#ffffff"><path
fill="#3389FF"
d="M31.435,99.967c-0.473,0-0.971-0.031-1.494-0.096c-3.67-0.46-6.486-2.862-7.531-6.428 c-1.109-3.789,0.051-8.048,2.826-10.358c1.144-0.955,2.845-0.8,3.797,0.342c0.953,1.146,0.798,2.845-0.346,3.799 c-1.138,0.947-1.612,2.968-1.104,4.701c0.308,1.049,1.082,2.351,3.025,2.594c1.404,0.177,2.473-0.065,3.259-0.735 c1.638-1.394,1.887-4.275,1.909-4.586V52.238c0-1.489,1.207-2.696,2.695-2.696s2.695,1.207,2.695,2.696v37.031 c0,0.032,0,0.062-0.002,0.097c-0.019,0.558-0.296,5.538-3.803,8.525C36.199,98.882,34.275,99.967,31.435,99.967z"
id="path27"
style="fill:#ffffff" /></g><g
id="g29"
style="fill:#ffffff"><path
fill="#3389FF"
d="M65.941,80.827c-2.84,0-4.764-1.085-5.928-2.076c-3.507-2.987-3.784-7.968-3.803-8.525 c-0.002-0.032-0.002-0.064-0.002-0.098V53.077c0-1.489,1.206-2.695,2.695-2.695c1.488,0,2.695,1.207,2.695,2.695v16.991 c0.05,0.889,0.482,3.377,1.923,4.591c0.786,0.66,1.849,0.899,3.245,0.723c1.942-0.242,2.718-1.544,3.025-2.595 c0.509-1.732,0.034-3.752-1.104-4.7c-1.144-0.953-1.299-2.652-0.345-3.797c0.951-1.144,2.65-1.299,3.796-0.345 c2.774,2.312,3.936,6.57,2.826,10.359c-1.045,3.565-3.861,5.969-7.53,6.428C66.912,80.796,66.414,80.827,65.941,80.827z"
id="path31"
style="fill:#ffffff" /></g><g
id="g33"
style="fill:#ffffff"><path
fill="#3389FF"
d="M55.773,99.967c-2.84,0-4.764-1.085-5.928-2.076c-3.507-2.987-3.783-7.968-3.803-8.525 c-0.002-0.034-0.002-0.064-0.002-0.097V52.238c0-1.489,1.207-2.696,2.696-2.696c1.488,0,2.694,1.207,2.694,2.696v36.967 c0.051,0.891,0.482,3.379,1.923,4.592c0.786,0.661,1.847,0.9,3.245,0.724c1.942-0.243,2.718-1.545,3.025-2.594 c0.509-1.733,0.034-3.754-1.104-4.701c-1.144-0.954-1.298-2.652-0.345-3.799c0.952-1.141,2.651-1.297,3.797-0.342 c2.774,2.311,3.935,6.569,2.825,10.358c-1.045,3.565-3.861,5.968-7.53,6.428C56.744,99.936,56.247,99.967,55.773,99.967z"
id="path35"
style="fill:#ffffff" /></g><g
id="g37"
style="fill:#ffffff"><path
fill="#3389FF"
d="M64.793,38.868c-0.178-1.947,3.787-1.169,3.311-2.94C63.147,17.484,43.646,8.512,43.632,8.506 c-0.015,0.006-19.516,8.978-24.472,27.422c-0.478,1.771,3.487,0.993,3.31,2.94c-0.217,2.364-4.765,3.333-4.172,10.121 c0.641,7.341,7.182,14.765,7.418,18.873h5.295v-0.153c0-1.319,1.069-2.388,2.389-2.388s2.388,1.068,2.388,2.388v0.153l5.39-2.695 V65.06c0-1.345,1.09-2.435,2.436-2.435c1.344,0,2.434,1.09,2.434,2.435v0.107l5.375,2.695v-0.153c0-1.319,1.069-2.388,2.389-2.388 s2.389,1.068,2.389,2.388v0.153h5.391c0.542-5.791,6.735-11.532,7.376-18.873C69.558,42.201,65.009,41.232,64.793,38.868z"
id="path39"
style="fill:#ffffff" /></g></g></svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

79
src/Squidex.Core/ContentEnricher.cs

@ -0,0 +1,79 @@
// ==========================================================================
// ContentEnricher.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using System.Collections.Generic;
namespace Squidex.Core
{
public sealed class ContentEnricher
{
private readonly Schema schema;
private readonly HashSet<Language> languages;
public ContentEnricher(HashSet<Language> languages, Schema schema)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
this.schema = schema;
this.languages = languages;
}
public void Enrich(ContentData data)
{
Guard.NotNull(data, nameof(data));
Guard.NotEmpty(languages, nameof(languages));
foreach (var field in schema.FieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
foreach (var language in languages)
{
Enrich(field, fieldData, language);
}
}
else
{
Enrich(field, fieldData, Language.Invariant);
}
if (fieldData.Count > 0)
{
data.AddField(field.Name, fieldData);
}
}
}
private static void Enrich(Field field, ContentFieldData fieldData, Language language)
{
Guard.NotNull(fieldData, nameof(fieldData));
Guard.NotNull(language, nameof(language));
var defaultValue = field.RawProperties.GetDefaultValue();
if (field.RawProperties.IsRequired || defaultValue.IsNull())
{
return;
}
if (!fieldData.TryGetValue(language.Iso2Code, out JToken value) || value == null || value.Type == JTokenType.Null)
{
fieldData.AddValue(language.Iso2Code, defaultValue);
}
}
}
}

52
src/Squidex.Core/ContentExtensions.cs

@ -0,0 +1,52 @@
// ==========================================================================
// ContentExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Core
{
public static class ContentExtensions
{
public static ContentData Enrich(this ContentData data, Schema schema, HashSet<Language> languages)
{
var validator = new ContentEnricher(languages, schema);
validator.Enrich(data);
return data;
}
public static async Task ValidateAsync(this ContentData data, Schema schema, HashSet<Language> languages, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, languages);
await validator.ValidateAsync(data);
foreach (var error in validator.Errors)
{
errors.Add(error);
}
}
public static async Task ValidatePartialAsync(this ContentData data, Schema schema, HashSet<Language> languages, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, languages);
await validator.ValidatePartialAsync(data);
foreach (var error in validator.Errors)
{
errors.Add(error);
}
}
}
}

190
src/Squidex.Core/ContentValidator.cs

@ -0,0 +1,190 @@
// ==========================================================================
// ContentValidator.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Core
{
public sealed class ContentValidator
{
private readonly Schema schema;
private readonly HashSet<Language> languages;
private readonly List<ValidationError> errors = new List<ValidationError>();
public ContentValidator(Schema schema, HashSet<Language> languages)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
this.schema = schema;
this.languages = languages;
}
public IReadOnlyList<ValidationError> Errors
{
get { return errors; }
}
public async Task ValidatePartialAsync(ContentData data)
{
Guard.NotNull(data, nameof(data));
foreach (var fieldData in data)
{
var fieldName = fieldData.Key;
if (!schema.FieldsByName.TryGetValue(fieldData.Key, out Field field))
{
AddError("<FIELD> is not a known field", fieldName);
}
else
{
if (field.RawProperties.IsLocalizable)
{
await ValidateLocalizableFieldPartialAsync(field, fieldData.Value);
}
else
{
await ValidateNonLocalizableFieldPartialAsync(field, fieldData.Value);
}
}
}
}
private async Task ValidateLocalizableFieldPartialAsync(Field field, ContentFieldData fieldData)
{
foreach (var languageValue in fieldData)
{
if (!Language.TryGetLanguage(languageValue.Key, out Language language))
{
AddError($"<FIELD> has an invalid language '{languageValue.Key}'", field);
}
else if (!languages.Contains(language))
{
AddError($"<FIELD> has an unsupported language '{languageValue.Key}'", field);
}
else
{
await ValidateAsync(field, languageValue.Value, language);
}
}
}
private async Task ValidateNonLocalizableFieldPartialAsync(Field field, ContentFieldData fieldData)
{
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
AddError($"<FIELD> can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})", field);
}
if (fieldData.TryGetValue(Language.Invariant.Iso2Code, out JToken value))
{
await ValidateAsync(field, value);
}
}
public async Task ValidateAsync(ContentData data)
{
Guard.NotNull(data, nameof(data));
ValidateUnknownFields(data);
foreach (var field in schema.FieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
await ValidateLocalizableFieldAsync(field, fieldData);
}
else
{
await ValidateNonLocalizableField(field, fieldData);
}
}
}
private void ValidateUnknownFields(ContentData data)
{
foreach (var fieldData in data)
{
if (!schema.FieldsByName.ContainsKey(fieldData.Key))
{
AddError("<FIELD> is not a known field", fieldData.Key);
}
}
}
private async Task ValidateLocalizableFieldAsync(Field field, ContentFieldData fieldData)
{
foreach (var valueLanguage in fieldData.Keys)
{
if (!Language.TryGetLanguage(valueLanguage, out Language language))
{
AddError($"<FIELD> has an invalid language '{valueLanguage}'", field);
}
else if (!languages.Contains(language))
{
AddError($"<FIELD> has an unsupported language '{valueLanguage}'", field);
}
}
foreach (var language in languages)
{
var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull());
await ValidateAsync(field, value, language);
}
}
private async Task ValidateNonLocalizableField(Field field, ContentFieldData fieldData)
{
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
AddError($"<FIELD> can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})", field);
}
var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
await ValidateAsync(field, value);
}
private Task ValidateAsync(Field field, JToken value, Language language = null)
{
return field.ValidateAsync(value, m => AddError(m, field, language));
}
private void AddError(string message, Field field, Language language = null)
{
var displayName = !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name;
if (language != null)
{
displayName += $" ({language.Iso2Code})";
}
message = message.Replace("<FIELD>", displayName);
errors.Add(new ValidationError(message, field.Name));
}
private void AddError(string message, string fieldName)
{
message = message.Replace("<FIELD>", fieldName);
errors.Add(new ValidationError(message, fieldName));
}
}
}

26
src/Squidex.Core/Contents/ContentData.cs

@ -62,6 +62,28 @@ namespace Squidex.Core.Contents
return result;
}
public ContentData ToCleaned()
{
var result = new ContentData();
foreach (var fieldValue in this.Where(x => x.Value != null))
{
var resultValue = new ContentFieldData();
foreach (var languageValue in fieldValue.Value.Where(x => x.Value != null && x.Value.Type != JTokenType.Null))
{
resultValue[languageValue.Key] = languageValue.Value;
}
if (resultValue.Count > 0)
{
result[fieldValue.Key] = resultValue;
}
}
return result;
}
public ContentData ToIdModel(Schema schema)
{
Guard.NotNull(schema, nameof(schema));
@ -130,6 +152,10 @@ namespace Squidex.Core.Contents
{
fieldResult.Add(languageCode, value);
}
else if (language.Equals(masterLanguage) && fieldValues.TryGetValue(invariantCode, out value))
{
fieldResult.Add(languageCode, value);
}
}
}
else

39
src/Squidex.Core/FieldExtensions.cs

@ -0,0 +1,39 @@
// ==========================================================================
// FieldExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Core
{
public static class FieldExtensions
{
public static async Task ValidateAsync(this Field field, JToken value, Action<string> addError)
{
Guard.NotNull(value, nameof(value));
try
{
var typedValue = value.IsNull() ? null : field.ConvertValue(value);
foreach (var validator in field.Validators)
{
await validator.ValidateAsync(typedValue, addError);
}
}
catch
{
addError("<FIELD> is not a valid value");
}
}
}
}

2
src/Squidex.Core/Identity/SquidexClaimTypes.cs

@ -8,7 +8,7 @@
namespace Squidex.Core.Identity
{
public class SquidexClaimTypes
public static class SquidexClaimTypes
{
public static readonly string SquidexDisplayName = "urn:squidex:name";

5
src/Squidex.Core/Schemas/BooleanField.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
@ -30,12 +31,12 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return (bool?)value;
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty)
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.Boolean;
}

3
src/Squidex.Core/Schemas/BooleanFieldEditor.cs

@ -10,6 +10,7 @@ namespace Squidex.Core.Schemas
{
public enum BooleanFieldEditor
{
Checkbox
Checkbox,
Toggle
}
}

6
src/Squidex.Core/Schemas/Cloneable.cs → src/Squidex.Core/Schemas/CloneableBase.cs

@ -1,5 +1,5 @@
// ==========================================================================
// Cloneable.cs
// CloneableBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -10,9 +10,9 @@ using System;
namespace Squidex.Core.Schemas
{
public abstract class Cloneable
public abstract class CloneableBase
{
protected T Clone<T>(Action<T> updater) where T : Cloneable
protected T Clone<T>(Action<T> updater) where T : CloneableBase
{
var clone = (T)MemberwiseClone();

10
src/Squidex.Core/Schemas/DateTimeField.cs

@ -17,6 +17,7 @@ using NodaTime.Text;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable ConvertIfStatementToSwitchStatement
@ -43,7 +44,7 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
if (value.Type == JTokenType.String)
{
@ -57,15 +58,10 @@ namespace Squidex.Core.Schemas
return parseResult.Value;
}
if (value.Type == JTokenType.Null)
{
return null;
}
throw new InvalidCastException("Invalid json type, expected string.");
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty)
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.String;

2
src/Squidex.Core/Schemas/DateTimeFieldProperties.cs

@ -67,7 +67,7 @@ namespace Squidex.Core.Schemas
public override JToken GetDefaultValue()
{
return DefaultValue != null ? DefaultValue.ToString() : null;
return DefaultValue?.ToString();
}
protected override IEnumerable<ValidationError> ValidateCore()

74
src/Squidex.Core/Schemas/Field.cs

@ -8,12 +8,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Contents;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
@ -22,7 +21,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
public abstract class Field : Cloneable
public abstract class Field : CloneableBase
{
private readonly Lazy<List<IValidator>> validators;
private readonly long id;
@ -50,6 +49,11 @@ namespace Squidex.Core.Schemas
get { return isDisabled; }
}
public IReadOnlyList<IValidator> Validators
{
get { return validators.Value; }
}
public abstract FieldProperties RawProperties { get; }
protected Field(long id, string name)
@ -66,53 +70,7 @@ namespace Squidex.Core.Schemas
public abstract Field Update(FieldProperties newProperties);
public void Enrich(ContentFieldData fieldData, Language language)
{
Guard.NotNull(fieldData, nameof(fieldData));
Guard.NotNull(language, nameof(language));
var defaultValue = RawProperties.GetDefaultValue();
if (!RawProperties.IsRequired && defaultValue != null && fieldData.GetOrDefault(language.Iso2Code) == null)
{
fieldData.AddValue(language.Iso2Code, defaultValue);
}
}
public async Task ValidateAsync(JToken value, ICollection<string> errors, Language language = null)
{
Guard.NotNull(value, nameof(value));
var rawErrors = new List<string>();
try
{
var typedValue = value.Type == JTokenType.Null ? null : ConvertValue(value);
foreach (var validator in validators.Value)
{
await validator.ValidateAsync(typedValue, rawErrors);
}
}
catch
{
rawErrors.Add("<FIELD> is not a valid value");
}
if (rawErrors.Count > 0)
{
var displayName = !string.IsNullOrWhiteSpace(RawProperties.Label) ? RawProperties.Label : name;
if (language != null)
{
displayName += $" ({language.Iso2Code})";
}
foreach (var error in rawErrors)
{
errors.Add(error.Replace("<FIELD>", displayName));
}
}
}
public abstract object ConvertValue(JToken value);
public Field Hide()
{
@ -174,7 +132,7 @@ namespace Squidex.Core.Schemas
edmType.AddStructuralProperty(Name, new EdmComplexTypeReference(languageType, false));
}
public void AddToSchema(JsonSchema4 schema, IEnumerable<Language> languages, string schemaName, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
public void AddToJsonSchema(JsonSchema4 schema, IEnumerable<Language> languages, string schemaName, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
@ -190,14 +148,14 @@ namespace Squidex.Core.Schemas
foreach (var language in languages)
{
var languageProperty = new JsonProperty { Description = language.EnglishName };
var languageProperty = new JsonProperty { Description = language.EnglishName, IsRequired = RawProperties.IsRequired };
PrepareJsonSchema(languageProperty);
PrepareJsonSchema(languageProperty, schemaResolver);
languagesObject.Properties.Add(language.Iso2Code, languageProperty);
}
languagesProperty.AllOf.Add(schemaResolver($"{schemaName}{Name.ToPascalCase()}Property", languagesObject));
languagesProperty.SchemaReference = schemaResolver($"{schemaName}{Name.ToPascalCase()}Property", languagesObject);
schema.Properties.Add(Name, languagesProperty);
}
@ -206,9 +164,9 @@ namespace Squidex.Core.Schemas
{
var jsonProperty = new JsonProperty { IsRequired = RawProperties.IsRequired, Type = JsonObjectType.Object };
if (!string.IsNullOrWhiteSpace(RawProperties.Label))
if (!string.IsNullOrWhiteSpace(RawProperties.Hints))
{
jsonProperty.Description = RawProperties.Label;
jsonProperty.Description = RawProperties.Hints;
}
else
{
@ -227,8 +185,6 @@ namespace Squidex.Core.Schemas
protected abstract IEdmTypeReference CreateEdmType();
protected abstract void PrepareJsonSchema(JsonProperty jsonProperty);
protected abstract object ConvertValue(JToken value);
protected abstract void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver);
}
}

2
src/Squidex.Core/Schemas/FieldProperties.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
public abstract class FieldProperties : NamedElementProperties, IValidatable
public abstract class FieldProperties : NamedElementPropertiesBase, IValidatable
{
private bool isRequired;
private bool isLocalizable;

5
src/Squidex.Core/Schemas/FieldRegistry.cs

@ -70,9 +70,12 @@ namespace Squidex.Core.Schemas
Add<JsonFieldProperties>(
(id, name, p) => new JsonField(id, name, (JsonFieldProperties)p));
Add<GeolocationFieldProperties>(
(id, name, p) => new GeolocationField(id, name, (GeolocationFieldProperties)p));
}
public void Add<TFieldProperties>(FactoryFunction fieldFactory)
private void Add<TFieldProperties>(FactoryFunction fieldFactory)
{
Guard.NotNull(fieldFactory, nameof(fieldFactory));

89
src/Squidex.Core/Schemas/GeolocationField.cs

@ -0,0 +1,89 @@
// ==========================================================================
// GeolocationField.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
public sealed class GeolocationField : Field<GeolocationFieldProperties>
{
public GeolocationField(long id, string name, GeolocationFieldProperties properties)
: base(id, name, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredValidator();
}
}
public override object ConvertValue(JToken value)
{
var geolocation = (JObject)value;
foreach (var property in geolocation.Properties())
{
if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidCastException("Geolocation can only have latitude and longitude property.");
}
}
var lat = (double)geolocation["latitude"];
var lon = (double)geolocation["longitude"];
Guard.Between(lat, -90, 90, "latitude");
Guard.Between(lon, -180, 180, "longitude");
return value;
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.Object;
var geolocationSchema = new JsonSchema4();
geolocationSchema.Properties.Add("latitude", new JsonProperty
{
Type = JsonObjectType.Number,
Minimum = -90,
Maximum = 90,
IsRequired = true
});
geolocationSchema.Properties.Add("longitude", new JsonProperty
{
Type = JsonObjectType.Number,
Minimum = -180,
Maximum = 180,
IsRequired = true
});
geolocationSchema.AllowAdditionalProperties = false;
var schemaReference = schemaResolver("GeolocationDto", geolocationSchema);
jsonProperty.SchemaReference = schemaReference;
}
protected override IEdmTypeReference CreateEdmType()
{
return null;
}
}
}

15
src/Squidex.Core/Schemas/GeolocationFieldEditor.cs

@ -0,0 +1,15 @@
// ==========================================================================
// GeolocationFieldEditor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Core.Schemas
{
public enum GeolocationFieldEditor
{
Map
}
}

44
src/Squidex.Core/Schemas/GeolocationFieldProperties.cs

@ -0,0 +1,44 @@
// ==========================================================================
// GeolocationFieldProperties.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
[TypeName("GeolocationField")]
public sealed class GeolocationFieldProperties : FieldProperties
{
private GeolocationFieldEditor editor;
public GeolocationFieldEditor Editor
{
get { return editor; }
set
{
ThrowIfFrozen();
editor = value;
}
}
public override JToken GetDefaultValue()
{
return null;
}
protected override IEnumerable<ValidationError> ValidateCore()
{
if (!Editor.IsEnumValue())
{
yield return new ValidationError("Editor ist not a valid value", nameof(Editor));
}
}
}
}

5
src/Squidex.Core/Schemas/JsonField.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Newtonsoft.Json.Linq;
@ -29,12 +30,12 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return value;
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty)
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.Object;
}

4
src/Squidex.Core/Schemas/NamedElementProperties.cs → src/Squidex.Core/Schemas/NamedElementPropertiesBase.cs

@ -1,5 +1,5 @@
// ==========================================================================
// NamedElementProperties.cs
// NamedElementPropertiesBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -10,7 +10,7 @@ using System;
namespace Squidex.Core.Schemas
{
public abstract class NamedElementProperties
public abstract class NamedElementPropertiesBase
{
private string label;
private string hints;

5
src/Squidex.Core/Schemas/NumberField.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
@ -41,7 +42,7 @@ namespace Squidex.Core.Schemas
}
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty)
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.Number;
@ -61,7 +62,7 @@ namespace Squidex.Core.Schemas
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, !Properties.IsRequired);
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return (double?)value;
}

3
src/Squidex.Core/Schemas/NumberFieldEditor.cs

@ -12,6 +12,7 @@ namespace Squidex.Core.Schemas
{
Input,
Radio,
Dropdown
Dropdown,
Stars
}
}

167
src/Squidex.Core/Schemas/Schema.cs

@ -10,11 +10,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Contents;
using Squidex.Infrastructure;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
@ -22,7 +19,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
public sealed class Schema : Cloneable
public sealed class Schema : CloneableBase
{
private readonly string name;
private readonly SchemaProperties properties;
@ -189,177 +186,21 @@ namespace Squidex.Core.Schemas
return edmType;
}
public JsonSchema4 BuildSchema(HashSet<Language> languages, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
public JsonSchema4 BuildJsonSchema(HashSet<Language> languages, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotEmpty(languages, nameof(languages));
Guard.NotNull(schemaResolver, nameof(schemaResolver));
var schemaName = Name.ToPascalCase();
var schema = new JsonSchema4 { Id = schemaName, Type = JsonObjectType.Object };
var schema = new JsonSchema4 { Type = JsonObjectType.Object };
foreach (var field in fieldsByName.Values.Where(x => !x.IsHidden))
{
field.AddToSchema(schema, languages, schemaName, schemaResolver);
field.AddToJsonSchema(schema, languages, schemaName, schemaResolver);
}
return schema;
}
public async Task ValidatePartialAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors));
foreach (var fieldData in data)
{
if (!fieldsByName.TryGetValue(fieldData.Key, out Field field))
{
errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
}
else
{
var fieldErrors = new List<string>();
if (field.RawProperties.IsLocalizable)
{
foreach (var languageValue in fieldData.Value)
{
if (!Language.TryGetLanguage(languageValue.Key, out Language language))
{
fieldErrors.Add($"{field.Name} has an invalid language '{languageValue.Key}'");
}
else if (!languages.Contains(language))
{
fieldErrors.Add($"{field.Name} has an unsupported language '{languageValue.Key}'");
}
else
{
await field.ValidateAsync(languageValue.Value, fieldErrors, language);
}
}
}
else
{
if (fieldData.Value.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
}
if (fieldData.Value.TryGetValue(Language.Invariant.Iso2Code, out JToken value))
{
await field.ValidateAsync(value, fieldErrors);
}
}
foreach (var error in fieldErrors)
{
errors.Add(new ValidationError(error, field.Name));
}
}
}
}
public async Task ValidateAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors));
Guard.NotEmpty(languages, nameof(languages));
ValidateUnknownFields(data, errors);
foreach (var field in fieldsByName.Values)
{
var fieldErrors = new List<string>();
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
await ValidateLocalizableFieldAsync(languages, fieldData, fieldErrors, field);
}
else
{
await ValidateNonLocalizableField(fieldData, fieldErrors, field);
}
foreach (var error in fieldErrors)
{
errors.Add(new ValidationError(error, field.Name));
}
}
}
private void ValidateUnknownFields(ContentData data, IList<ValidationError> errors)
{
foreach (var fieldData in data)
{
if (!fieldsByName.ContainsKey(fieldData.Key))
{
errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
}
}
}
private static async Task ValidateLocalizableFieldAsync(HashSet<Language> languages, ContentFieldData fieldData, List<string> fieldErrors, Field field)
{
foreach (var valueLanguage in fieldData.Keys)
{
if (!Language.TryGetLanguage(valueLanguage, out Language language))
{
fieldErrors.Add($"{field.Name} has an invalid language '{valueLanguage}'");
}
else if (!languages.Contains(language))
{
fieldErrors.Add($"{field.Name} has an unsupported language '{valueLanguage}'");
}
}
foreach (var language in languages)
{
var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull());
await field.ValidateAsync(value, fieldErrors, language);
}
}
private static async Task ValidateNonLocalizableField(ContentFieldData fieldData, List<string> fieldErrors, Field field)
{
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
}
var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
await field.ValidateAsync(value, fieldErrors);
}
public void Enrich(ContentData data, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotEmpty(languages, nameof(languages));
foreach (var field in fieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
if (field.RawProperties.IsLocalizable)
{
foreach (var language in languages)
{
field.Enrich(fieldData, language);
}
}
else
{
field.Enrich(fieldData, Language.Invariant);
}
if (fieldData.Count > 0)
{
data.AddField(field.Name, fieldData);
}
}
}
}
}

2
src/Squidex.Core/Schemas/SchemaProperties.cs

@ -8,7 +8,7 @@
namespace Squidex.Core.Schemas
{
public sealed class SchemaProperties : NamedElementProperties
public sealed class SchemaProperties : NamedElementPropertiesBase
{
}
}

5
src/Squidex.Core/Schemas/StringField.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@ -47,7 +48,7 @@ namespace Squidex.Core.Schemas
}
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty)
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.String;
@ -65,7 +66,7 @@ namespace Squidex.Core.Schemas
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, !Properties.IsRequired);
}
protected override object ConvertValue(JToken value)
public override object ConvertValue(JToken value)
{
return value.ToString();
}

2
src/Squidex.Core/Schemas/StringFieldEditor.cs

@ -10,9 +10,9 @@ namespace Squidex.Core.Schemas
{
public enum StringFieldEditor
{
Dropdown,
Input,
Markdown,
Dropdown,
Radio,
RichText,
TextArea

6
src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
@ -25,7 +25,7 @@ namespace Squidex.Core.Schemas.Validators
this.allowedValues = allowedValues;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value == null)
{
@ -36,7 +36,7 @@ namespace Squidex.Core.Schemas.Validators
if (!allowedValues.Contains(typedValue))
{
errors.Add("<FIELD> is not an allowed value");
addError("<FIELD> is not an allowed value");
}
return TaskHelper.Done;

6
src/Squidex.Core/Schemas/IValidator.cs → src/Squidex.Core/Schemas/Validators/IValidator.cs

@ -6,13 +6,13 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
namespace Squidex.Core.Schemas
namespace Squidex.Core.Schemas.Validators
{
public interface IValidator
{
Task ValidateAsync(object value, ICollection<string> errors);
Task ValidateAsync(object value, Action<string> addError);
}
}

28
src/Squidex.Core/Schemas/Validators/PatternValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -28,24 +28,20 @@ namespace Squidex.Core.Schemas.Validators
regex = new Regex("^" + pattern + "$");
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
var stringValue = value as string;
if (stringValue == null)
{
return TaskHelper.Done;
}
if (!regex.IsMatch(stringValue))
if (value is string stringValue)
{
if (string.IsNullOrWhiteSpace(errorMessage))
{
errors.Add("<FIELD> is not valid");
}
else
if (!regex.IsMatch(stringValue))
{
errors.Add(errorMessage);
if (string.IsNullOrWhiteSpace(errorMessage))
{
addError("<FIELD> is not valid");
}
else
{
addError(errorMessage);
}
}
}

7
src/Squidex.Core/Schemas/Validators/RangeValidator.cs

@ -7,7 +7,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -29,7 +28,7 @@ namespace Squidex.Core.Schemas.Validators
this.max = max;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value == null)
{
@ -40,12 +39,12 @@ namespace Squidex.Core.Schemas.Validators
if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{
errors.Add($"<FIELD> must be greater than '{min}'");
addError($"<FIELD> must be greater than '{min}'");
}
if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{
errors.Add($"<FIELD> must be less than '{max}'");
addError($"<FIELD> must be less than '{max}'");
}
return TaskHelper.Done;

6
src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -21,7 +21,7 @@ namespace Squidex.Core.Schemas.Validators
this.validateEmptyStrings = validateEmptyStrings;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value != null && !(value is string))
{
@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas.Validators
if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString)))
{
errors.Add("<FIELD> is required");
addError("<FIELD> is required");
}
return TaskHelper.Done;

6
src/Squidex.Core/Schemas/Validators/RequiredValidator.cs

@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@ -14,11 +14,11 @@ namespace Squidex.Core.Schemas.Validators
{
public class RequiredValidator : IValidator
{
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
if (value == null)
{
errors.Add("<FIELD> is required");
addError("<FIELD> is required");
}
return TaskHelper.Done;

29
src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs

@ -7,10 +7,11 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
namespace Squidex.Core.Schemas.Validators
{
public class StringLengthValidator : IValidator
@ -29,23 +30,19 @@ namespace Squidex.Core.Schemas.Validators
this.maxLength = maxLength;
}
public Task ValidateAsync(object value, ICollection<string> errors)
public Task ValidateAsync(object value, Action<string> addError)
{
var stringValue = value as string;
if (stringValue == null)
{
return TaskHelper.Done;
}
if (minLength.HasValue && stringValue.Length < minLength.Value)
{
errors.Add($"<FIELD> must have more than '{minLength}' characters");
}
if (maxLength.HasValue && stringValue.Length > maxLength.Value)
if (value is string stringValue)
{
errors.Add($"<FIELD> must have less than '{maxLength}' characters");
if (minLength.HasValue && stringValue.Length < minLength.Value)
{
addError($"<FIELD> must have more than '{minLength}' characters");
}
if (maxLength.HasValue && stringValue.Length > maxLength.Value)
{
addError($"<FIELD> must have less than '{maxLength}' characters");
}
}
return TaskHelper.Done;

2
src/Squidex.Core/Squidex.Core.csproj

@ -14,7 +14,7 @@
<PackageReference Include="protobuf-net" Version="2.1.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
<PackageReference Include="NodaTime" Version="2.0.0-beta20170123" />
<PackageReference Include="NJsonSchema" Version="8.5.6255.20253" />
<PackageReference Include="NJsonSchema" Version="8.10.6282.29572" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="Microsoft.OData.Core" Version="6.15.0" />

234
src/Squidex.Events/.gitignore

@ -1,234 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

2
src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs

@ -12,7 +12,7 @@ using Squidex.Core.Schemas;
namespace Squidex.Events.Schemas.Utils
{
public class SchemaEventDispatcher
public static class SchemaEventDispatcher
{
public static Schema Dispatch(SchemaCreated @event)
{

2
src/Squidex.Events/SquidexEvent.cs

@ -1,5 +1,5 @@
// ==========================================================================
// SquidexEvent.cs
// SquidexEventBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group

14
src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs

@ -69,15 +69,17 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{
await Collection.Find(x => x.EventStream == streamName).ForEachAsync(commit =>
{
var position = commit.EventStreamOffset;
var eventNumber = commit.EventsOffset;
var eventStreamNumber = commit.EventStreamOffset;
foreach (var @event in commit.Events)
{
var eventData = SimpleMapper.Map(@event, new EventData());
eventNumber++;
eventStreamNumber++;
observer.OnNext(new StoredEvent(position, eventData));
var eventData = SimpleMapper.Map(@event, new EventData());
position++;
observer.OnNext(new StoredEvent(eventNumber, eventStreamNumber, eventData));
}
}, ct);
});
@ -92,16 +94,18 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
await Collection.Find(x => x.EventsOffset >= commitOffset).SortBy(x => x.EventsOffset).ForEachAsync(commit =>
{
var eventNumber = commit.EventsOffset;
var eventStreamNumber = commit.EventStreamOffset;
foreach (var @event in commit.Events)
{
eventNumber++;
eventStreamNumber++;
if (eventNumber > lastReceivedEventNumber)
{
var eventData = SimpleMapper.Map(@event, new EventData());
observer.OnNext(new StoredEvent(eventNumber, eventData));
observer.OnNext(new StoredEvent(eventNumber, eventStreamNumber, eventData));
}
}
}, ct);

25
src/Squidex.Infrastructure.MongoDb/InstantSerializer.cs

@ -9,6 +9,7 @@
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using NodaTime;
using System;
// ReSharper disable InvertIf
@ -16,26 +17,16 @@ namespace Squidex.Infrastructure.MongoDb
{
public sealed class InstantSerializer : SerializerBase<Instant>, IBsonPolymorphicSerializer
{
private static bool isRegistered;
private static readonly object LockObject = new object();
private static readonly Lazy<bool> Registerer = new Lazy<bool>(() =>
{
BsonSerializer.RegisterSerializer(new InstantSerializer());
return true;
});
public static bool Register()
{
if (!isRegistered)
{
lock (LockObject)
{
if (!isRegistered)
{
BsonSerializer.RegisterSerializer(new InstantSerializer());
isRegistered = true;
return true;
}
}
}
return false;
return !Registerer.IsValueCreated && Registerer.Value;
}
public bool IsDiscriminatorCompatibleWithObjectSerializer

15
src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs

@ -11,6 +11,7 @@ using System.Globalization;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.MongoDb
{
@ -29,7 +30,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
protected ProjectionDefinitionBuilder<TEntity> Projection
protected static ProjectionDefinitionBuilder<TEntity> Projection
{
get
{
@ -37,7 +38,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
protected SortDefinitionBuilder<TEntity> Sort
protected static SortDefinitionBuilder<TEntity> Sort
{
get
{
@ -45,7 +46,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
protected UpdateDefinitionBuilder<TEntity> Update
protected static UpdateDefinitionBuilder<TEntity> Update
{
get
{
@ -53,7 +54,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
protected FilterDefinitionBuilder<TEntity> Filter
protected static FilterDefinitionBuilder<TEntity> Filter
{
get
{
@ -61,7 +62,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
protected IndexKeysDefinitionBuilder<TEntity> IndexKeys
protected static IndexKeysDefinitionBuilder<TEntity> IndexKeys
{
get
{
@ -127,7 +128,7 @@ namespace Squidex.Infrastructure.MongoDb
protected virtual Task SetupCollectionAsync(IMongoCollection<TEntity> collection)
{
return Task.FromResult(true);
return TaskHelper.Done;
}
public virtual Task ClearAsync()
@ -135,7 +136,7 @@ namespace Squidex.Infrastructure.MongoDb
return Collection.DeleteManyAsync(new BsonDocument());
}
public async Task<bool> TryDropCollectionAsync()
public async Task<bool> DropCollectionIfExistsAsync()
{
try
{

25
src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
@ -15,26 +16,16 @@ namespace Squidex.Infrastructure.MongoDb
{
public class RefTokenSerializer : SerializerBase<RefToken>
{
private static bool isRegistered;
private static readonly object LockObject = new object();
private static readonly Lazy<bool> Registerer = new Lazy<bool>(() =>
{
BsonSerializer.RegisterSerializer(new RefTokenSerializer());
return true;
});
public static bool Register()
{
if (!isRegistered)
{
lock (LockObject)
{
if (!isRegistered)
{
BsonSerializer.RegisterSerializer(new RefTokenSerializer());
isRegistered = true;
return true;
}
}
}
return false;
return !Registerer.IsValueCreated && Registerer.Value;
}
public override RefToken Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)

2
src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -10,6 +10,6 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.4.2" />
<PackageReference Include="MongoDB.Driver" Version="2.4.3" />
</ItemGroup>
</Project>

2
src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Squidex.Infrastructure.Redis
{
public class RedisInfrastructureErrors
public static class RedisInfrastructureErrors
{
public static readonly EventId InvalidatingReceivedFailed = new EventId(50001, "InvalidingReceivedFailed");

4
src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.csproj

@ -10,7 +10,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.0" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.1" />
</ItemGroup>
</Project>

59
src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs

@ -7,7 +7,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Events;
@ -17,7 +16,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
private readonly IDomainObjectRepository domainObjectRepository;
private readonly IDomainObjectFactory domainObjectFactory;
private readonly IEnumerable<IEventProcessor> eventProcessors;
public IDomainObjectRepository Repository
{
@ -31,55 +29,72 @@ namespace Squidex.Infrastructure.CQRS.Commands
public AggregateHandler(
IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository,
IEnumerable<IEventProcessor> eventProcessors)
IDomainObjectRepository domainObjectRepository)
{
Guard.NotNull(eventProcessors, nameof(eventProcessors));
Guard.NotNull(domainObjectFactory, nameof(domainObjectFactory));
Guard.NotNull(domainObjectRepository, nameof(domainObjectRepository));
this.domainObjectFactory = domainObjectFactory;
this.domainObjectRepository = domainObjectRepository;
this.eventProcessors = eventProcessors;
}
public async Task CreateAsync<T>(IAggregateCommand command, Func<T, Task> creator) where T : class, IAggregate
public async Task CreateAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IAggregate
{
Guard.NotNull(creator, nameof(creator));
Guard.NotNull(command, nameof(command));
Guard.NotEmpty(command.AggregateId, nameof(command.AggregateId));
Guard.NotNull(context, nameof(context));
var aggregate = domainObjectFactory.CreateNew<T>(command.AggregateId);
var aggregateCommand = GetCommand(context);
var aggregate = (T)domainObjectFactory.CreateNew(typeof(T), aggregateCommand.AggregateId);
await creator(aggregate);
await Save(command, aggregate);
await SaveAsync(aggregate);
if (!context.IsHandled)
{
context.Succeed(new EntityCreatedResult<Guid>(aggregate.Id, aggregate.Version));
}
}
public async Task UpdateAsync<T>(IAggregateCommand command, Func<T, Task> updater) where T : class, IAggregate
public async Task UpdateAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IAggregate
{
Guard.NotNull(updater, nameof(updater));
Guard.NotNull(command, nameof(command));
Guard.NotEmpty(command.AggregateId, nameof(command.AggregateId));
Guard.NotNull(context, nameof(context));
var aggregate = await domainObjectRepository.GetByIdAsync<T>(command.AggregateId);
var aggregateCommand = GetCommand(context);
var aggregate = await domainObjectRepository.GetByIdAsync<T>(aggregateCommand.AggregateId, aggregateCommand.ExpectedVersion);
await updater(aggregate);
await Save(command, aggregate);
await SaveAsync(aggregate);
if (!context.IsHandled)
{
context.Succeed(new EntitySavedResult(aggregate.Version));
}
}
private static IAggregateCommand GetCommand(CommandContext context)
{
var command = context.Command as IAggregateCommand;
if (command == null)
{
throw new ArgumentException("Context must have an aggregate command.", nameof(context));
}
Guard.NotEmpty(command.AggregateId, "context.Command.AggregateId");
return command;
}
private async Task Save(ICommand command, IAggregate aggregate)
private async Task SaveAsync(IAggregate aggregate)
{
var events = aggregate.GetUncomittedEvents();
foreach (var @event in events)
{
foreach (var eventProcessor in eventProcessors)
{
await eventProcessor.ProcessEventAsync(@event, aggregate, command);
}
@event.SetAggregateId(aggregate.Id);
}
await domainObjectRepository.SaveAsync(aggregate, events, Guid.NewGuid());

17
src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs

@ -14,14 +14,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public static class CommandingExtensions
{
public static T CreateNew<T>(this IDomainObjectFactory factory, Guid id) where T : IAggregate
public static Task CreateAsync<T>(this IAggregateHandler handler, CommandContext context, Action<T> creator) where T : class, IAggregate
{
return (T)factory.CreateNew(typeof(T), id);
}
public static Task CreateAsync<T>(this IAggregateHandler handler, IAggregateCommand command, Action<T> creator) where T : class, IAggregate
{
return handler.CreateAsync<T>(command, x =>
return handler.CreateAsync<T>(context, x =>
{
creator(x);
@ -29,12 +24,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
});
}
public static Task UpdateAsync<T>(this IAggregateHandler handler, IAggregateCommand command, Action<T> creator) where T : class, IAggregate
public static Task UpdateAsync<T>(this IAggregateHandler handler, CommandContext context, Action<T> updater) where T : class, IAggregate
{
return handler.UpdateAsync<T>(command, x =>
return handler.UpdateAsync<T>(context, x =>
{
creator(x);
updater(x);
return TaskHelper.Done;
});
}

9
src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectFactory.cs

@ -28,7 +28,14 @@ namespace Squidex.Infrastructure.CQRS.Commands
var factoryFunctionType = typeof(DomainObjectFactoryFunction<>).MakeGenericType(type);
var factoryFunction = (Delegate)serviceProvider.GetService(factoryFunctionType);
return (IAggregate)factoryFunction.DynamicInvoke(id);
var aggregate = (IAggregate)factoryFunction.DynamicInvoke(id);
if (aggregate.Version != -1)
{
throw new InvalidOperationException("Must have a version of -1");
}
return aggregate;
}
}
}

11
src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs

@ -39,10 +39,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
this.nameResolver = nameResolver;
}
public async Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate
public async Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, long? expectedVersion = null) where TDomainObject : class, IAggregate
{
Guard.GreaterThan(version, 0, nameof(version));
var streamName = nameResolver.GetStreamName(typeof(TDomainObject), id);
var events = await eventStore.GetEventsAsync(streamName).ToList();
@ -61,9 +59,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
domainObject.ApplyEvent(envelope);
}
if (domainObject.Version != version && version < int.MaxValue)
if (expectedVersion != null && domainObject.Version != expectedVersion.Value)
{
throw new DomainObjectVersionException(id.ToString(), typeof(TDomainObject), domainObject.Version, version);
throw new DomainObjectVersionException(id.ToString(), typeof(TDomainObject), domainObject.Version, expectedVersion.Value);
}
return domainObject;
@ -76,8 +74,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id);
var versionCurrent = domainObject.Version;
var versionBefore = versionCurrent - events.Count;
var versionExpected = versionBefore == 0 ? -1 : versionBefore - 1;
var versionExpected = versionCurrent - events.Count;
var eventsToSave = events.Select(x => formatter.ToEventData(x, commitId)).ToList();

7
src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using NodaTime;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Commands
{
@ -24,14 +25,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
public Task<bool> HandleAsync(CommandContext context)
{
var timestampCommand = context.Command as ITimestampCommand;
if (timestampCommand != null)
if (context.Command is ITimestampCommand timestampCommand)
{
timestampCommand.Timestamp = clock.GetCurrentInstant();
}
return Task.FromResult(false);
return TaskHelper.False;
}
}
}

18
src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs

@ -0,0 +1,18 @@
// ==========================================================================
// EntityCreatedResult.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure.CQRS.Commands
{
public static class EntityCreatedResult
{
public static EntityCreatedResult<T> Create<T>(T idOrValue, long version)
{
return new EntityCreatedResult<T>(idOrValue, version);
}
}
}

18
src/Squidex/Config/Web/WebpackServices.cs → src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs

@ -1,23 +1,21 @@
// ==========================================================================
// WebpackServices.cs
// EntityCreatedResult_T.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Squidex.Pipeline;
namespace Squidex.Config.Web
namespace Squidex.Infrastructure.CQRS.Commands
{
public static class WebpackServices
public sealed class EntityCreatedResult<T> : EntitySavedResult
{
public static IServiceCollection AddWebpackBuilder(this IServiceCollection services)
{
services.AddSingleton<WebpackRunner>();
public T IdOrValue { get; }
return services;
public EntityCreatedResult(T idOrValue, long version)
: base(version)
{
IdOrValue = idOrValue;
}
}
}

20
src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs

@ -0,0 +1,20 @@
// ==========================================================================
// EntitySavedResult.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure.CQRS.Commands
{
public class EntitySavedResult
{
public long Version { get; }
public EntitySavedResult(long version)
{
Version = version;
}
}
}

4
src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs

@ -13,8 +13,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface IAggregateHandler
{
Task CreateAsync<T>(IAggregateCommand command, Func<T, Task> creator) where T : class, IAggregate;
Task CreateAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IAggregate;
Task UpdateAsync<T>(IAggregateCommand command, Func<T, Task> updater) where T : class, IAggregate;
Task UpdateAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IAggregate;
}
}

1
src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs

@ -10,5 +10,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface ICommand
{
long? ExpectedVersion { get; set; }
}
}

2
src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs

@ -15,7 +15,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface IDomainObjectRepository
{
Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate;
Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, long? expectedVersion = null) where TDomainObject : class, IAggregate;
Task SaveAsync(IAggregate domainObject, ICollection<Envelope<IEvent>> events, Guid commitId);
}

3
src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
@ -36,7 +37,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
logger.LogCritical(InfrastructureErrors.CommandUnknown, exception, "Unknown command {0}", context.Command);
}
return Task.FromResult(false);
return TaskHelper.False;
}
}
}

3
src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Commands
{
@ -24,7 +25,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
logger.LogInformation("Handling {0} command", context.Command);
return Task.FromResult(false);
return TaskHelper.False;
}
}
}

6
src/Squidex.Infrastructure/CQRS/DomainObject.cs → src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Infrastructure.CQRS
{
public abstract class DomainObject : IAggregate, IEquatable<IAggregate>
public abstract class DomainObjectBase : IAggregate, IEquatable<IAggregate>
{
private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>();
private readonly Guid id;
@ -28,10 +28,10 @@ namespace Squidex.Infrastructure.CQRS
get { return id; }
}
protected DomainObject(Guid id, int version)
protected DomainObjectBase(Guid id, int version)
{
Guard.NotEmpty(id, nameof(id));
Guard.GreaterEquals(version, 0, nameof(version));
Guard.GreaterEquals(version, -1, nameof(version));
this.id = id;

6
src/Squidex.Infrastructure/CQRS/CommonHeaders.cs → src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs

@ -6,9 +6,9 @@
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure.CQRS
namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class CommonHeaders
public static class CommonHeaders
{
public static readonly string AggregateId = "AggregateId";
@ -18,6 +18,8 @@ namespace Squidex.Infrastructure.CQRS
public static readonly string EventNumber = "EventNumber";
public static readonly string EventStreamNumber = "EventStreamNumber";
public static readonly string Timestamp = "Timestamp";
public static readonly string Actor = "Actor";

2
src/Squidex.Infrastructure/CQRS/Events/DefaultMemoryEventNotifier.cs

@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class DefaultMemoryEventNotifier : IEventNotifier
{
private readonly string ChannelName = typeof(DefaultMemoryEventNotifier).Name;
private static readonly string ChannelName = typeof(DefaultMemoryEventNotifier).Name;
private readonly IPubSub invalidator;

29
src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs

@ -1,29 +0,0 @@
// ==========================================================================
// EnrichWithAggregateIdProcessor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class EnrichWithAggregateIdProcessor : IEventProcessor
{
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command)
{
var aggregateCommand = command as IAggregateCommand;
if (aggregateCommand != null)
{
@event.SetAggregateId(aggregateCommand.AggregateId);
}
return TaskHelper.Done;
}
}
}

2
src/Squidex.Infrastructure/CQRS/Envelope.cs → src/Squidex.Infrastructure/CQRS/Events/Envelope.cs

@ -9,7 +9,7 @@
using System;
using NodaTime;
namespace Squidex.Infrastructure.CQRS
namespace Squidex.Infrastructure.CQRS.Events
{
public class Envelope<TPayload> where TPayload : class
{

14
src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs → src/Squidex.Infrastructure/CQRS/Events/EnvelopeExtensions.cs

@ -10,7 +10,7 @@ using System;
using System.Globalization;
using NodaTime;
namespace Squidex.Infrastructure.CQRS
namespace Squidex.Infrastructure.CQRS.Events
{
public static class EnvelopeExtensions
{
@ -26,6 +26,18 @@ namespace Squidex.Infrastructure.CQRS
return envelope;
}
public static long EventStreamNumber(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.EventStreamNumber].ToInt32(CultureInfo.InvariantCulture);
}
public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventStreamNumber, value);
return envelope;
}
public static Guid CommitId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.CommitId].ToGuid(CultureInfo.InvariantCulture);

3
src/Squidex.Infrastructure/CQRS/EnvelopeHeaders.cs → src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs

@ -5,7 +5,8 @@
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure.CQRS
namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class EnvelopeHeaders : PropertiesBag
{

3
src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs

@ -18,7 +18,7 @@ using Squidex.Infrastructure.Timers;
namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class EventReceiver : DisposableObject
public sealed class EventReceiver : DisposableObjectBase
{
private readonly EventDataFormatter formatter;
private readonly IEventStore eventStore;
@ -176,6 +176,7 @@ namespace Squidex.Infrastructure.CQRS.Events
var @event = formatter.Parse(storedEvent.Data);
@event.SetEventNumber(storedEvent.EventNumber);
@event.SetEventStreamNumber(storedEvent.EventStreamNumber);
return @event;
}

10
src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs

@ -11,6 +11,7 @@ namespace Squidex.Infrastructure.CQRS.Events
public sealed class StoredEvent
{
private readonly long eventNumber;
private readonly long eventStreamNumber;
private readonly EventData data;
public long EventNumber
@ -18,18 +19,23 @@ namespace Squidex.Infrastructure.CQRS.Events
get { return eventNumber; }
}
public long EventStreamNumber
{
get { return eventStreamNumber; }
}
public EventData Data
{
get { return data; }
}
public StoredEvent(long eventNumber, EventData data)
public StoredEvent(long eventNumber, long eventStreamNumber, EventData data)
{
Guard.NotNull(data, nameof(data));
this.data = data;
this.eventNumber = eventNumber;
this.eventStreamNumber = eventStreamNumber;
}
}
}

2
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -30,7 +30,7 @@ namespace Squidex.Infrastructure
{
if (item != null)
{
hashCode = hashCode * 23 + item.GetHashCode();
hashCode = hashCode * 23 + comparer.GetHashCode(item);
}
}

2
src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs

@ -13,7 +13,7 @@ using System.Reflection;
namespace Squidex.Infrastructure.Dispatching
{
internal class ActionContextDispatcherFactory
internal static class ActionContextDispatcherFactory
{
public static Tuple<Type, Action<TTarget, object, TContext>> CreateActionHandler<TTarget, TContext>(MethodInfo methodInfo)
{

2
src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs

@ -13,7 +13,7 @@ using System.Reflection;
namespace Squidex.Infrastructure.Dispatching
{
internal class ActionDispatcherFactory
internal static class ActionDispatcherFactory
{
public static Tuple<Type, Action<T, object>> CreateActionHandler<T>(MethodInfo methodInfo)
{

4
src/Squidex.Infrastructure/DisposableObject.cs → src/Squidex.Infrastructure/DisposableObjectBase.cs

@ -1,5 +1,5 @@
// ==========================================================================
// EnumExtensions.cs
// DisposableObjectBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -10,7 +10,7 @@ using System;
namespace Squidex.Infrastructure
{
public abstract class DisposableObject : IDisposable
public abstract class DisposableObjectBase : IDisposable
{
private readonly object disposeLock = new object();
private bool isDisposed;

12
src/Squidex.Infrastructure/DomainObjectVersionException.cs

@ -12,20 +12,20 @@ namespace Squidex.Infrastructure
{
public class DomainObjectVersionException : DomainObjectException
{
private readonly int currentVersion;
private readonly int expectedVersion;
private readonly long currentVersion;
private readonly long expectedVersion;
public int CurrentVersion
public long CurrentVersion
{
get { return currentVersion; }
}
public int ExpectedVersion
public long ExpectedVersion
{
get { return expectedVersion; }
}
public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion)
public DomainObjectVersionException(string id, Type type, long currentVersion, long expectedVersion)
: base(FormatMessage(id, type, currentVersion, expectedVersion), id, type)
{
this.currentVersion = currentVersion;
@ -33,7 +33,7 @@ namespace Squidex.Infrastructure
this.expectedVersion = expectedVersion;
}
private static string FormatMessage(string id, Type type, int currentVersion, int expectedVersion)
private static string FormatMessage(string id, Type type, long currentVersion, long expectedVersion)
{
return $"Requested version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}.";
}

44
src/Squidex.Infrastructure/Json/ConverterContractResolver.cs

@ -0,0 +1,44 @@
// ==========================================================================
// ConverterContractResolver.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Squidex.Infrastructure.Json
{
public sealed class ConverterContractResolver : CamelCasePropertyNamesContractResolver
{
private readonly JsonConverter[] converters;
public ConverterContractResolver(params JsonConverter[] converters)
{
this.converters = converters;
}
protected override JsonConverter ResolveContractConverter(Type objectType)
{
var result = base.ResolveContractConverter(objectType);
if (result != null)
{
return result;
}
foreach (var converter in converters)
{
if (converter.CanConvert(objectType))
{
return converter;
}
}
return null;
}
}
}

33
src/Squidex.Infrastructure/Json/JsonExtension.cs

@ -0,0 +1,33 @@
// ==========================================================================
// JsonExtension.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.Json
{
public static class JsonExtension
{
public static bool IsNull(this JToken token)
{
if (token == null)
{
return true;
}
if (token.Type == JTokenType.Null)
{
return true;
}
if (token is JValue value)
{
return value.Value == null;
}
return false;
}
}
}

14
src/Squidex.Infrastructure/Language.cs

@ -19,7 +19,7 @@ namespace Squidex.Infrastructure
private static readonly Regex CultureRegex = new Regex("([a-z]{2})(\\-[a-z]{2})?");
private readonly string iso2Code;
private readonly string englishName;
private static readonly Dictionary<string, Language> allLanguages = new Dictionary<string, Language>();
private static readonly Dictionary<string, Language> AllLanguagesField = new Dictionary<string, Language>();
public static readonly Language Invariant = AddLanguage("iv", "Invariant");
@ -27,7 +27,7 @@ namespace Squidex.Infrastructure
{
var language = new Language(iso2Code, englishName);
allLanguages[iso2Code] = language;
AllLanguagesField[iso2Code] = language;
return language;
}
@ -38,7 +38,7 @@ namespace Squidex.Infrastructure
try
{
return allLanguages[iso2Code];
return AllLanguagesField[iso2Code];
}
catch (KeyNotFoundException)
{
@ -48,7 +48,7 @@ namespace Squidex.Infrastructure
public static IEnumerable<Language> AllLanguages
{
get { return allLanguages.Values; }
get { return AllLanguagesField.Values; }
}
public string EnglishName
@ -72,17 +72,17 @@ namespace Squidex.Infrastructure
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.ContainsKey(iso2Code);
return AllLanguagesField.ContainsKey(iso2Code);
}
public static bool TryGetLanguage(string iso2Code, out Language language)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.TryGetValue(iso2Code, out language);
return AllLanguagesField.TryGetValue(iso2Code, out language);
}
public static Language TryParse(string input)
public static Language ParseOrNull(string input)
{
if (string.IsNullOrWhiteSpace(input))
{

4
src/Squidex.Infrastructure/Languages.cs

@ -5,9 +5,13 @@
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
// <autogenerated/>
using System.CodeDom.Compiler;
namespace Squidex.Infrastructure
{
[GeneratedCode("LanguagesGenerator", "1.0")]
partial class Language
{
public static readonly Language AA = AddLanguage("aa", "Afar");

4
src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs

@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.Reflection
}
else
{
getMethod = x => throw new NotSupportedException();
getMethod = x => { throw new NotSupportedException(); };
}
if (propertyInfo.CanWrite)
@ -35,7 +35,7 @@ namespace Squidex.Infrastructure.Reflection
}
else
{
setMethod = (x, y) => throw new NotSupportedException();
setMethod = (x, y) => { throw new NotSupportedException(); };
}
}

2
src/Squidex.Infrastructure/Security/OpenIdClaims.cs

@ -8,7 +8,7 @@
namespace Squidex.Infrastructure.Security
{
public class OpenIdClaims
public static class OpenIdClaims
{
/// <summary>
/// Unique Identifier for the End-User at the Issuer.

4
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -7,8 +7,8 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.2-beta2" />
<PackageReference Include="NodaTime" Version="2.0.0-beta20170123" />
<PackageReference Include="System.Linq" Version="4.3.0" />

11
src/Squidex.Infrastructure/Tasks/TaskHelper.cs

@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Tasks
public static class TaskHelper
{
public static readonly Task Done = CreateDoneTask();
public static readonly Task<bool> False = CreateResultTask(false);
public static readonly Task<bool> True = CreateResultTask(true);
private static Task CreateDoneTask()
{
@ -22,5 +24,14 @@ namespace Squidex.Infrastructure.Tasks
return result.Task;
}
private static Task<bool> CreateResultTask(bool value)
{
var result = new TaskCompletionSource<bool>();
result.SetResult(value);
return result.Task;
}
}
}

2
src/Squidex.Infrastructure/Timers/CompletionTimer.cs

@ -14,7 +14,7 @@ using System.Threading.Tasks;
namespace Squidex.Infrastructure.Timers
{
public sealed class CompletionTimer : DisposableObject
public sealed class CompletionTimer : DisposableObjectBase
{
private readonly CancellationTokenSource disposeToken = new CancellationTokenSource();
private readonly Task runTask;

6
src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs

@ -13,6 +13,8 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps;
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
namespace Squidex.Read.MongoDb.Apps
{
public sealed class MongoAppEntity : MongoEntity, IAppEntity
@ -25,6 +27,10 @@ namespace Squidex.Read.MongoDb.Apps
[BsonElement]
public string MasterLanguage { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
public HashSet<string> Languages { get; set; } = new HashSet<string>();

1
src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Squidex.Events;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;

4
src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs

@ -36,6 +36,10 @@ namespace Squidex.Read.MongoDb.Contents
[BsonElement]
public string Text { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }

43
src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs

@ -12,12 +12,13 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.Core;
using MongoDB.Driver;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Contents;
using Squidex.Read.Contents.Builders;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.MongoDb.Contents.Visitors;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services;
namespace Squidex.Read.MongoDb.Contents
@ -26,7 +27,8 @@ namespace Squidex.Read.MongoDb.Contents
{
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
private readonly ISchemaProvider schemaProvider;
private readonly ISchemaProvider schemas;
private readonly EdmModelBuilder modelBuilder;
protected static IndexKeysDefinitionBuilder<MongoContentEntity> IndexKeys
{
@ -36,28 +38,31 @@ namespace Squidex.Read.MongoDb.Contents
}
}
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemaProvider)
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas, EdmModelBuilder modelBuilder)
{
Guard.NotNull(database, nameof(database));
Guard.NotNull(schemaProvider, nameof(schemaProvider));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemas, nameof(schemas));
this.schemas = schemas;
this.database = database;
this.schemaProvider = schemaProvider;
this.modelBuilder = modelBuilder;
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, string odataQuery, HashSet<Language> languages)
{
List<IContentEntity> result = null;
await ForSchemaAsync(schemaId, async (collection, schema) =>
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
var parser = schema.ParseQuery(languages, odataQuery);
var model = modelBuilder.BuildEdmModel(schemaEntity, languages);
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schema);
cursor = collection.Find(parser, schemaEntity.Schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schemaEntity.Schema);
}
catch (NotSupportedException)
{
@ -76,7 +81,7 @@ namespace Squidex.Read.MongoDb.Contents
foreach (var entity in entities)
{
entity.ParseData(schema);
entity.ParseData(schemaEntity.Schema);
}
result = entities.OfType<IContentEntity>().ToList();
@ -89,14 +94,16 @@ namespace Squidex.Read.MongoDb.Contents
{
var result = 0L;
await ForSchemaAsync(schemaId, async (collection, schema) =>
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
var parser = schema.ParseQuery(languages, odataQuery);
var model = modelBuilder.BuildEdmModel(schemaEntity, languages);
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, schema, nonPublished);
cursor = collection.Find(parser, schemaEntity.Schema, nonPublished);
}
catch (NotSupportedException)
{
@ -121,28 +128,28 @@ namespace Squidex.Read.MongoDb.Contents
{
MongoContentEntity result = null;
await ForSchemaAsync(schemaId, async (collection, schema) =>
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
result = await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
result?.ParseData(schema);
result?.ParseData(schemaEntity.Schema);
});
return result;
}
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Schema, Task> action)
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntityWithSchema, Task> action)
{
var collection = GetCollection(schemaId);
var schemaEntity = await schemaProvider.FindSchemaByIdAsync(schemaId);
var schemaEntity = await schemas.FindSchemaByIdAsync(schemaId, true);
if (schemaEntity == null)
{
return;
}
await action(collection, schemaEntity.Schema);
await action(collection, schemaEntity);
}
}
}

17
src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -12,7 +12,6 @@ using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Events.Contents;
using Squidex.Events.Schemas;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
@ -68,24 +67,24 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.SchemaId.Id, (collection, schema) =>
return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.CreateAsync(@event, headers, x =>
{
SimpleMapper.Map(@event, x);
x.SetData(schema, @event.Data);
x.SetData(schemaEntity.Schema, @event.Data);
});
});
}
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.SchemaId.Id, (collection, schema) =>
return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.UpdateAsync(@event, headers, x =>
{
x.SetData(schema, @event.Data);
x.SetData(schemaEntity.Schema, @event.Data);
});
});
}
@ -112,19 +111,19 @@ namespace Squidex.Read.MongoDb.Contents
});
}
protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
{
return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition<MongoContentEntity>($"Data.{@event.FieldId}")));
return collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
});
}
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
{
return collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition<MongoContentEntity>($"Data.{@event.FieldId}")));
});
}

27
src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs

@ -0,0 +1,27 @@
// ==========================================================================
// SchemaExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.Core.UriParser;
using Microsoft.OData.Edm;
namespace Squidex.Read.MongoDb.Contents.Visitors
{
public static class EdmModelExtensions
{
public static ODataUriParser ParseQuery(this IEdmModel model, string query)
{
var path = model.EntityContainer.EntitySets().First().Path.Path.Last().Split('.').Last();
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative));
return parser;
}
}
}

4
src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs

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

1
src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs

@ -13,7 +13,6 @@ using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Events;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.History;

8
src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs

@ -33,6 +33,10 @@ namespace Squidex.Read.MongoDb.Schemas
[BsonElement]
public string Schema { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
@ -49,6 +53,10 @@ namespace Squidex.Read.MongoDb.Schemas
[BsonElement]
public bool IsPublished { get; set; }
[BsonRequired]
[BsonElement]
public bool IsDeleted { get; set; }
Schema ISchemaEntityWithSchema.Schema
{
get { return schema.Value; }

15
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs

@ -49,14 +49,14 @@ namespace Squidex.Read.MongoDb.Schemas
public async Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId)
{
var entities = await Collection.Find(s => s.AppId == appId).ToListAsync();
var entities = await Collection.Find(s => s.AppId == appId && !s.IsDeleted).ToListAsync();
return entities.OfType<ISchemaEntity>().ToList();
}
public async Task<IReadOnlyList<ISchemaEntityWithSchema>> QueryAllWithSchemaAsync(Guid appId)
{
var entities = await Collection.Find(s => s.AppId == appId).ToListAsync();
var entities = await Collection.Find(s => s.AppId == appId && !s.IsDeleted).ToListAsync();
entities.ForEach(x => x.DeserializeSchema(serializer));
@ -66,7 +66,7 @@ namespace Squidex.Read.MongoDb.Schemas
public async Task<ISchemaEntityWithSchema> FindSchemaAsync(Guid appId, string name)
{
var entity =
await Collection.Find(s => s.Name == name && s.AppId == appId)
await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted)
.FirstOrDefaultAsync();
entity?.DeserializeSchema(serializer);
@ -84,14 +84,5 @@ namespace Squidex.Read.MongoDb.Schemas
return entity;
}
public async Task<Guid?> FindSchemaIdAsync(Guid appId, string name)
{
var entity =
await Collection.Find(s => s.Name == name & s.AppId == appId)
.Project<MongoSchemaEntity>(Projection.Include(x => x.Id)).FirstOrDefaultAsync();
return entity?.Id;
}
}
}

3
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -14,7 +14,6 @@ using Squidex.Events;
using Squidex.Events.Schemas;
using Squidex.Events.Schemas.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
@ -92,7 +91,7 @@ namespace Squidex.Read.MongoDb.Schemas
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
await Collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
await Collection.UpdateAsync(@event, headers, e => e.IsDeleted = true);
SchemaSaved?.Invoke(@event.AppId, @event.SchemaId);
}

6
src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj

@ -15,10 +15,10 @@
<ProjectReference Include="..\Squidex.Read\Squidex.Read.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="1.1.0" />
<PackageReference Include="IdentityServer4" Version="1.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.MongoDB" Version="1.0.2" />
<PackageReference Include="MongoDB.Driver" Version="2.4.2" />
<PackageReference Include="MongoDB.Driver" Version="2.4.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="Microsoft.OData.Core" Version="6.15.0" />

29
src/Squidex.Read.MongoDb/Utils/EntityMapper.cs

@ -7,7 +7,7 @@
// ==========================================================================
using Squidex.Events;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
@ -23,6 +23,7 @@ namespace Squidex.Read.MongoDb.Utils
SetId(headers, entity);
SetVersion(headers, entity);
SetCreated(headers, entity);
SetCreatedBy(@event, entity);
@ -33,6 +34,7 @@ namespace Squidex.Read.MongoDb.Utils
public static T Update<T>(SquidexEvent @event, EnvelopeHeaders headers, T entity) where T : MongoEntity, new()
{
SetVersion(headers, entity);
SetLastModified(headers, entity);
SetLastModifiedBy(@event, entity);
@ -54,32 +56,33 @@ namespace Squidex.Read.MongoDb.Utils
entity.LastModified = headers.Timestamp();
}
private static void SetCreatedBy(SquidexEvent @event, MongoEntity entity)
private static void SetVersion(EnvelopeHeaders headers, MongoEntity entity)
{
var createdBy = entity as ITrackCreatedByEntity;
if (entity is IEntityWithVersion withVersion)
{
withVersion.Version = headers.EventStreamNumber();
}
}
if (createdBy != null)
private static void SetCreatedBy(SquidexEvent @event, MongoEntity entity)
{
if (entity is IEntityWithCreatedBy withCreatedBy)
{
createdBy.CreatedBy = @event.Actor;
withCreatedBy.CreatedBy = @event.Actor;
}
}
private static void SetLastModifiedBy(SquidexEvent @event, MongoEntity entity)
{
var modifiedBy = entity as ITrackLastModifiedByEntity;
if (modifiedBy != null)
if (entity is IEntityWithLastModifiedBy withModifiedBy)
{
modifiedBy.LastModifiedBy = @event.Actor;
withModifiedBy.LastModifiedBy = @event.Actor;
}
}
private static void SetAppId(SquidexEvent @event, MongoEntity entity)
{
var appEntity = entity as IAppRefEntity;
var appEvent = @event as AppEvent;
if (appEntity != null && appEvent != null)
if (entity is IAppRefEntity appEntity && @event is AppEvent appEvent)
{
appEntity.AppId = appEvent.AppId.Id;
}

2
src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs

@ -11,7 +11,7 @@ using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Read.MongoDb.Utils

1
src/Squidex.Read/Apps/AppHistoryEventsCreator.cs

@ -9,7 +9,6 @@
using System.Threading.Tasks;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Read.History;

2
src/Squidex.Read/Apps/IAppEntity.cs

@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Read.Apps
{
public interface IAppEntity : IEntity
public interface IAppEntity : IEntity, IEntityWithVersion
{
string Name { get; }

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

Loading…
Cancel
Save