Browse Source

Merge pull request #303 from Squidex/dnc_21

Dotnet Core 2.1
pull/304/head v1.7.0
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
b45745187f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 4
      Dockerfile
  3. 2
      Dockerfile.build
  4. 11
      libs/Dockerfile
  5. 8
      src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  6. 14
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  7. 16
      src/Squidex.Domain.Apps.Core/Apps/AppClientPermission.cs
  8. 16
      src/Squidex.Domain.Apps.Core/Apps/AppContributorPermission.cs
  9. 17
      src/Squidex.Domain.Apps.Core/Apps/AppPermission.cs
  10. 29
      src/Squidex.Domain.Apps.Core/Apps/RoleExtension.cs
  11. 71
      src/Squidex.Domain.Apps.Core/ContentEnricher.cs
  12. 49
      src/Squidex.Domain.Apps.Core/ContentExtensions.cs
  13. 141
      src/Squidex.Domain.Apps.Core/ContentValidator.cs
  14. 122
      src/Squidex.Domain.Apps.Core/Contents/ContentData.cs
  15. 55
      src/Squidex.Domain.Apps.Core/Contents/ContentFieldData.cs
  16. 130
      src/Squidex.Domain.Apps.Core/Contents/IdContentData.cs
  17. 191
      src/Squidex.Domain.Apps.Core/Contents/NamedContentData.cs
  18. 16
      src/Squidex.Domain.Apps.Core/Contents/Status.cs
  19. 32
      src/Squidex.Domain.Apps.Core/Contents/StatusFlow.cs
  20. 57
      src/Squidex.Domain.Apps.Core/FieldExtensions.cs
  21. 22
      src/Squidex.Domain.Apps.Core/IFieldPartitionItem.cs
  22. 18
      src/Squidex.Domain.Apps.Core/IFieldPartitioning.cs
  23. 72
      src/Squidex.Domain.Apps.Core/InvariantPartitioning.cs
  24. 58
      src/Squidex.Domain.Apps.Core/LanguageConfig.cs
  25. 218
      src/Squidex.Domain.Apps.Core/LanguagesConfig.cs
  26. 49
      src/Squidex.Domain.Apps.Core/Partitioning.cs
  27. 26
      src/Squidex.Domain.Apps.Core/PartitioningExtensions.cs
  28. 79
      src/Squidex.Domain.Apps.Core/Schemas/AssetsField.cs
  29. 57
      src/Squidex.Domain.Apps.Core/Schemas/AssetsFieldProperties.cs
  30. 44
      src/Squidex.Domain.Apps.Core/Schemas/BooleanField.cs
  31. 15
      src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldEditor.cs
  32. 57
      src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldProperties.cs
  33. 23
      src/Squidex.Domain.Apps.Core/Schemas/CloneableBase.cs
  34. 15
      src/Squidex.Domain.Apps.Core/Schemas/DateTimeCalculatedDefaultValue.cs
  35. 64
      src/Squidex.Domain.Apps.Core/Schemas/DateTimeField.cs
  36. 15
      src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldEditor.cs
  37. 115
      src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldProperties.cs
  38. 59
      src/Squidex.Domain.Apps.Core/Schemas/Edm/EdmSchemaExtensions.cs
  39. 75
      src/Squidex.Domain.Apps.Core/Schemas/Edm/EdmTypeVisitor.cs
  40. 130
      src/Squidex.Domain.Apps.Core/Schemas/Field.cs
  41. 70
      src/Squidex.Domain.Apps.Core/Schemas/FieldProperties.cs
  42. 115
      src/Squidex.Domain.Apps.Core/Schemas/FieldRegistry.cs
  43. 55
      src/Squidex.Domain.Apps.Core/Schemas/Field{T}.cs
  44. 63
      src/Squidex.Domain.Apps.Core/Schemas/GeolocationField.cs
  45. 14
      src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldEditor.cs
  46. 42
      src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldProperties.cs
  47. 30
      src/Squidex.Domain.Apps.Core/Schemas/IFieldPropertiesVisitor.cs
  48. 30
      src/Squidex.Domain.Apps.Core/Schemas/IFieldVisitor.cs
  49. 20
      src/Squidex.Domain.Apps.Core/Schemas/IReferenceField.cs
  50. 35
      src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs
  51. 86
      src/Squidex.Domain.Apps.Core/Schemas/Json/JsonSchemaModel.cs
  52. 40
      src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaConverter.cs
  53. 44
      src/Squidex.Domain.Apps.Core/Schemas/JsonField.cs
  54. 26
      src/Squidex.Domain.Apps.Core/Schemas/JsonFieldProperties.cs
  55. 55
      src/Squidex.Domain.Apps.Core/Schemas/JsonSchema/ContentSchemaBuilder.cs
  56. 70
      src/Squidex.Domain.Apps.Core/Schemas/JsonSchema/JsonSchemaExtensions.cs
  57. 161
      src/Squidex.Domain.Apps.Core/Schemas/JsonSchema/JsonTypeVisitor.cs
  58. 60
      src/Squidex.Domain.Apps.Core/Schemas/NamedElementPropertiesBase.cs
  59. 55
      src/Squidex.Domain.Apps.Core/Schemas/NumberField.cs
  60. 17
      src/Squidex.Domain.Apps.Core/Schemas/NumberFieldEditor.cs
  61. 103
      src/Squidex.Domain.Apps.Core/Schemas/NumberFieldProperties.cs
  62. 87
      src/Squidex.Domain.Apps.Core/Schemas/ReferencesField.cs
  63. 73
      src/Squidex.Domain.Apps.Core/Schemas/ReferencesFieldProperties.cs
  64. 170
      src/Squidex.Domain.Apps.Core/Schemas/Schema.cs
  65. 13
      src/Squidex.Domain.Apps.Core/Schemas/SchemaProperties.cs
  66. 60
      src/Squidex.Domain.Apps.Core/Schemas/StringField.cs
  67. 19
      src/Squidex.Domain.Apps.Core/Schemas/StringFieldEditor.cs
  68. 139
      src/Squidex.Domain.Apps.Core/Schemas/StringFieldProperties.cs
  69. 49
      src/Squidex.Domain.Apps.Core/Schemas/TagsField.cs
  70. 57
      src/Squidex.Domain.Apps.Core/Schemas/TagsFieldProperties.cs
  71. 58
      src/Squidex.Domain.Apps.Core/Schemas/ValidationContext.cs
  72. 44
      src/Squidex.Domain.Apps.Core/Schemas/Validators/AllowedValuesValidator.cs
  73. 29
      src/Squidex.Domain.Apps.Core/Schemas/Validators/AssetsValidator.cs
  74. 47
      src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionItemValidator.cs
  75. 53
      src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionValidator.cs
  76. 17
      src/Squidex.Domain.Apps.Core/Schemas/Validators/IValidator.cs
  77. 47
      src/Squidex.Domain.Apps.Core/Schemas/Validators/PatternValidator.cs
  78. 52
      src/Squidex.Domain.Apps.Core/Schemas/Validators/RangeValidator.cs
  79. 36
      src/Squidex.Domain.Apps.Core/Schemas/Validators/ReferencesValidator.cs
  80. 40
      src/Squidex.Domain.Apps.Core/Schemas/Validators/RequiredStringValidator.cs
  81. 26
      src/Squidex.Domain.Apps.Core/Schemas/Validators/RequiredValidator.cs
  82. 48
      src/Squidex.Domain.Apps.Core/Schemas/Validators/StringLengthValidator.cs
  83. 131
      src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentDataObject.cs
  84. 67
      src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentDataProperty.cs
  85. 136
      src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentFieldObject.cs
  86. 58
      src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentFieldProperty.cs
  87. 145
      src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/JsonMapper.cs
  88. 20
      src/Squidex.Domain.Apps.Core/Scripting/IScriptEngine.cs
  89. 177
      src/Squidex.Domain.Apps.Core/Scripting/JintScriptEngine.cs
  90. 49
      src/Squidex.Domain.Apps.Core/Scripting/JintUser.cs
  91. 26
      src/Squidex.Domain.Apps.Core/Scripting/ScriptContext.cs
  92. 29
      src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj
  93. 24
      src/Squidex.Domain.Apps.Core/Webhooks/WebhookSchema.cs
  94. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  95. 8
      src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  96. 8
      src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  97. 8
      src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  98. 8
      src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  99. 2
      src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
  100. 2
      src/Squidex.Infrastructure.GoogleCloud/Squidex.Infrastructure.GoogleCloud.csproj

5
CHANGELOG.md

@ -1,5 +1,9 @@
# Changelog # Changelog
## v1.7.0 - 2018-06-25
* Migration to .NET Core 2.1
## v1.6.2 - 2018-06-23 ## v1.6.2 - 2018-06-23
### Features ### Features
@ -12,7 +16,6 @@
* **Schemas**: Invariant name handling for field names. * **Schemas**: Invariant name handling for field names.
## v1.6.1 - 2018-06-22 ## v1.6.1 - 2018-06-22
### Bugfixes ### Bugfixes

4
Dockerfile

@ -1,7 +1,7 @@
# #
# Stage 1, Prebuild # Stage 1, Prebuild
# #
FROM squidex/aspnetcore-build-phantomjs-chromium:2.0.3-jessie-fix1 as builder FROM squidex/dotnet:2.1-sdk-chromium-phantomjs-node as builder
COPY src/Squidex/package.json /tmp/package.json COPY src/Squidex/package.json /tmp/package.json
@ -33,7 +33,7 @@ RUN dotnet publish src/Squidex/Squidex.csproj --output /out/ --configuration Rel
# #
# Stage 2, Build runtime # Stage 2, Build runtime
# #
FROM microsoft/aspnetcore:2.0.3-jessie FROM microsoft/dotnet:2.1.0-aspnetcore-runtime
# Default AspNetCore directory # Default AspNetCore directory
WORKDIR /app WORKDIR /app

2
Dockerfile.build

@ -1,4 +1,4 @@
FROM squidex/aspnetcore-build-phantomjs-chromium:2.0.3-jessie-fix1 as builder FROM squidex/dotnet:2.1-sdk-chromium-phantomjs-node as builder
COPY src/Squidex/package.json /tmp/package.json COPY src/Squidex/package.json /tmp/package.json

11
libs/Dockerfile

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore-build:2.0.3-jessie FROM microsoft/dotnet:2.1-sdk
# Install runtime dependencies # Install runtime dependencies
RUN apt-get update \ RUN apt-get update \
@ -24,6 +24,15 @@ RUN set -x \
RUN phantomjs --version RUN phantomjs --version
# Install Node
ENV NODE_VERSION 8.9.4
ENV NODE_DOWNLOAD_SHA 21fb4690e349f82d708ae766def01d7fec1b085ce1f5ab30d9bda8ee126ca8fc
RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" --output nodejs.tar.gz \
&& echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
&& tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
&& rm nodejs.tar.gz \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
# Install Google Chrome # Install Google Chrome
# See https://crbug.com/795759 # See https://crbug.com/795759

8
src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
@ -8,10 +8,10 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Fody" Version="3.0.3" /> <PackageReference Include="Fody" Version="3.1.3" />
<PackageReference Include="Freezable.Fody" Version="1.9.0" /> <PackageReference Include="Freezable.Fody" Version="1.9.1" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" /> <PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />

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

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
@ -14,18 +14,18 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Algolia.Search" Version="4.2.2" /> <PackageReference Include="Algolia.Search" Version="5.0.0" />
<PackageReference Include="Elasticsearch.Net" Version="6.0.2" /> <PackageReference Include="Elasticsearch.Net" Version="6.1.0" />
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.4.4" /> <PackageReference Include="Microsoft.OData.Core" Version="7.4.4" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NJsonSchema" Version="9.10.35" /> <PackageReference Include="NJsonSchema" Version="9.10.56" />
<PackageReference Include="NodaTime" Version="2.2.5" /> <PackageReference Include="NodaTime" Version="2.3.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" /> <PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.1.1" /> <PackageReference Include="WindowsAzure.Storage" Version="9.2.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

16
src/Squidex.Domain.Apps.Core/Apps/AppClientPermission.cs

@ -1,16 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Apps
{
public enum AppClientPermission
{
Developer,
Editor,
Reader
}
}

16
src/Squidex.Domain.Apps.Core/Apps/AppContributorPermission.cs

@ -1,16 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Apps
{
public enum AppContributorPermission
{
Owner,
Developer,
Editor
}
}

17
src/Squidex.Domain.Apps.Core/Apps/AppPermission.cs

@ -1,17 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Apps
{
public enum AppPermission
{
Owner,
Developer,
Editor,
Reader
}
}

29
src/Squidex.Domain.Apps.Core/Apps/RoleExtension.cs

@ -1,29 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
public static class RoleExtension
{
public static AppPermission ToAppPermission(this AppClientPermission clientPermission)
{
Guard.Enum(clientPermission, nameof(clientPermission));
return (AppPermission)Enum.Parse(typeof(AppPermission), clientPermission.ToString());
}
public static AppPermission ToAppPermission(this AppContributorPermission contributorPermission)
{
Guard.Enum(contributorPermission, nameof(contributorPermission));
return (AppPermission)Enum.Parse(typeof(AppPermission), contributorPermission.ToString());
}
}
}

71
src/Squidex.Domain.Apps.Core/ContentEnricher.cs

@ -1,71 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core
{
public sealed class ContentEnricher<T>
{
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
public ContentEnricher(Schema schema, PartitionResolver partitionResolver)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
this.schema = schema;
this.partitionResolver = partitionResolver;
}
public void Enrich(ContentData<T> data)
{
Guard.NotNull(data, nameof(data));
foreach (var field in schema.Fields)
{
var fieldKey = data.GetKey(field);
var fieldData = data.GetOrCreate(fieldKey, k => new ContentFieldData());
var fieldPartition = partitionResolver(field.Partitioning);
foreach (var partitionItem in fieldPartition)
{
Enrich(field, fieldData, partitionItem);
}
if (fieldData.Count > 0)
{
data[fieldKey] = fieldData;
}
}
}
private static void Enrich(Field field, ContentFieldData fieldData, IFieldPartitionItem partitionItem)
{
Guard.NotNull(fieldData, nameof(fieldData));
var defaultValue = field.RawProperties.GetDefaultValue();
if (field.RawProperties.IsRequired || defaultValue.IsNull())
{
return;
}
var key = partitionItem.Key;
if (!fieldData.TryGetValue(key, out var value) || field.RawProperties.ShouldApplyDefaultValue(value))
{
fieldData.AddValue(key, defaultValue);
}
}
}
}

49
src/Squidex.Domain.Apps.Core/ContentExtensions.cs

@ -1,49 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core
{
public static class ContentExtensions
{
public static void Enrich<T>(this ContentData<T> data, Schema schema, PartitionResolver partitionResolver)
{
var enricher = new ContentEnricher<T>(schema, partitionResolver);
enricher.Enrich(data);
}
public static async Task ValidateAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, partitionResolver, context);
await validator.ValidateAsync(data);
foreach (var error in validator.Errors)
{
errors.Add(error);
}
}
public static async Task ValidatePartialAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, partitionResolver, context);
await validator.ValidatePartialAsync(data);
foreach (var error in validator.Errors)
{
errors.Add(error);
}
}
}
}

141
src/Squidex.Domain.Apps.Core/ContentValidator.cs

@ -1,141 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
#pragma warning disable 168
namespace Squidex.Domain.Apps.Core
{
public sealed class ContentValidator
{
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context;
private readonly ConcurrentBag<ValidationError> errors = new ConcurrentBag<ValidationError>();
public IReadOnlyCollection<ValidationError> Errors
{
get { return errors; }
}
public ContentValidator(Schema schema, PartitionResolver partitionResolver, ValidationContext context)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
this.schema = schema;
this.context = context;
this.partitionResolver = partitionResolver;
}
public Task ValidatePartialAsync(NamedContentData data)
{
Guard.NotNull(data, nameof(data));
var tasks = new List<Task>();
foreach (var fieldData in data)
{
var fieldName = fieldData.Key;
if (!schema.FieldsByName.TryGetValue(fieldData.Key, out var field))
{
errors.AddError("<FIELD> is not a known field.", fieldName);
}
else
{
tasks.Add(ValidateFieldPartialAsync(field, fieldData.Value));
}
}
return Task.WhenAll(tasks);
}
private Task ValidateFieldPartialAsync(Field field, ContentFieldData fieldData)
{
var partitioning = field.Partitioning;
var partition = partitionResolver(partitioning);
var tasks = new List<Task>();
foreach (var partitionValues in fieldData)
{
if (partition.TryGetItem(partitionValues.Key, out var item))
{
tasks.Add(field.ValidateAsync(partitionValues.Value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item)));
}
else
{
errors.AddError($"<FIELD> has an unsupported {partitioning.Key} value '{partitionValues.Key}'.", field);
}
}
return Task.WhenAll(tasks);
}
public Task ValidateAsync(NamedContentData data)
{
Guard.NotNull(data, nameof(data));
ValidateUnknownFields(data);
var tasks = new List<Task>();
foreach (var field in schema.FieldsByName.Values)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
tasks.Add(ValidateFieldAsync(field, fieldData));
}
return Task.WhenAll(tasks);
}
private void ValidateUnknownFields(NamedContentData data)
{
foreach (var fieldData in data)
{
if (!schema.FieldsByName.ContainsKey(fieldData.Key))
{
errors.AddError("<FIELD> is not a known field.", fieldData.Key);
}
}
}
private Task ValidateFieldAsync(Field field, ContentFieldData fieldData)
{
var partitioning = field.Partitioning;
var partition = partitionResolver(partitioning);
var tasks = new List<Task>();
foreach (var partitionValues in fieldData)
{
if (!partition.TryGetItem(partitionValues.Key, out var _))
{
errors.AddError($"<FIELD> has an unsupported {partitioning.Key} value '{partitionValues.Key}'.", field);
}
}
foreach (var item in partition)
{
var value = fieldData.GetOrCreate(item.Key, k => JValue.CreateNull());
tasks.Add(field.ValidateAsync(value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item)));
}
return Task.WhenAll(tasks);
}
}
}

122
src/Squidex.Domain.Apps.Core/Contents/ContentData.cs

@ -1,122 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.Contents
{
public abstract class ContentData<T> : Dictionary<T, ContentFieldData>, IEquatable<ContentData<T>>
{
public IEnumerable<KeyValuePair<T, ContentFieldData>> ValidValues
{
get { return this.Where(x => x.Value != null); }
}
protected ContentData(IEqualityComparer<T> comparer)
: base(comparer)
{
}
protected ContentData(IDictionary<T, ContentFieldData> copy, IEqualityComparer<T> comparer)
: base(copy, comparer)
{
}
protected static TResult Merge<TResult>(TResult source, TResult target) where TResult : ContentData<T>
{
if (ReferenceEquals(target, source))
{
return source;
}
foreach (var otherValue in source)
{
var fieldValue = target.GetOrAdd(otherValue.Key, x => new ContentFieldData());
foreach (var value in otherValue.Value)
{
fieldValue[value.Key] = value.Value;
}
}
return target;
}
protected static TResult Clean<TResult>(TResult source, TResult target) where TResult : ContentData<T>
{
foreach (var fieldValue in source.ValidValues)
{
var resultValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value.Where(x => !x.Value.IsNull()))
{
resultValue[partitionValue.Key] = partitionValue.Value;
}
if (resultValue.Count > 0)
{
target[fieldValue.Key] = resultValue;
}
}
return target;
}
public IEnumerable<Guid> GetReferencedIds(Schema schema)
{
Guard.NotNull(schema, nameof(schema));
var foundReferences = new HashSet<Guid>();
foreach (var field in schema.Fields)
{
if (field is IReferenceField referenceField)
{
var fieldKey = GetKey(field);
var fieldData = this.GetOrDefault(fieldKey);
if (fieldData == null)
{
continue;
}
foreach (var partitionValue in fieldData.Where(x => x.Value != null))
{
var ids = referenceField.GetReferencedIds(partitionValue.Value);
foreach (var id in ids.Where(x => foundReferences.Add(x)))
{
yield return id;
}
}
}
}
}
public override bool Equals(object obj)
{
return Equals(obj as ContentData<T>);
}
public bool Equals(ContentData<T> other)
{
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
}
public override int GetHashCode()
{
return this.DictionaryHashCode();
}
public abstract T GetKey(Field field);
}
}

55
src/Squidex.Domain.Apps.Core/Contents/ContentFieldData.cs

@ -1,55 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Contents
{
public sealed class ContentFieldData : Dictionary<string, JToken>, IEquatable<ContentFieldData>
{
private static readonly JTokenEqualityComparer JTokenEqualityComparer = new JTokenEqualityComparer();
public ContentFieldData()
: base(StringComparer.OrdinalIgnoreCase)
{
}
public ContentFieldData SetValue(JToken value)
{
this[InvariantPartitioning.Instance.Master.Key] = value;
return this;
}
public ContentFieldData AddValue(string key, JToken value)
{
Guard.NotNullOrEmpty(key, nameof(key));
this[key] = value;
return this;
}
public override bool Equals(object obj)
{
return Equals(obj as ContentFieldData);
}
public bool Equals(ContentFieldData other)
{
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other, EqualityComparer<string>.Default, JTokenEqualityComparer));
}
public override int GetHashCode()
{
return this.DictionaryHashCode(EqualityComparer<string>.Default, JTokenEqualityComparer);
}
}
}

130
src/Squidex.Domain.Apps.Core/Contents/IdContentData.cs

@ -1,130 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.Contents
{
public sealed class IdContentData : ContentData<long>, IEquatable<IdContentData>
{
public IdContentData()
: base(EqualityComparer<long>.Default)
{
}
public IdContentData(IdContentData copy)
: base(copy, EqualityComparer<long>.Default)
{
}
public IdContentData MergeInto(IdContentData target)
{
return Merge(this, target);
}
public IdContentData ToCleaned()
{
return Clean(this, new IdContentData());
}
public IdContentData AddField(long id, ContentFieldData data)
{
Guard.GreaterThan(id, 0, nameof(id));
this[id] = data;
return this;
}
public IdContentData ToCleanedReferences(Schema schema, ISet<Guid> deletedReferencedIds)
{
var result = new IdContentData(this);
foreach (var field in schema.Fields)
{
if (field is IReferenceField referenceField)
{
var fieldKey = GetKey(field);
var fieldData = this.GetOrDefault(fieldKey);
if (fieldData == null)
{
continue;
}
foreach (var partitionValue in fieldData.Where(x => !x.Value.IsNull()).ToList())
{
var newValue = referenceField.RemoveDeletedReferences(partitionValue.Value, deletedReferencedIds);
fieldData[partitionValue.Key] = newValue;
}
}
}
return result;
}
public NamedContentData ToNameModel(Schema schema, bool decodeJsonField)
{
Guard.NotNull(schema, nameof(schema));
var result = new NamedContentData();
foreach (var fieldValue in this)
{
if (!schema.FieldsById.TryGetValue(fieldValue.Key, out var field))
{
continue;
}
if (decodeJsonField && field is JsonField)
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value)
{
if (partitionValue.Value.IsNull())
{
encodedValue[partitionValue.Key] = null;
}
else
{
var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString()));
encodedValue[partitionValue.Key] = JToken.Parse(value);
}
}
result[field.Name] = encodedValue;
}
else
{
result[field.Name] = fieldValue.Value;
}
}
return result;
}
public bool Equals(IdContentData other)
{
return base.Equals(other);
}
public override long GetKey(Field field)
{
return field.Id;
}
}
}

191
src/Squidex.Domain.Apps.Core/Contents/NamedContentData.cs

@ -1,191 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.Contents
{
public sealed class NamedContentData : ContentData<string>, IEquatable<NamedContentData>
{
public NamedContentData()
: base(StringComparer.OrdinalIgnoreCase)
{
}
public NamedContentData MergeInto(NamedContentData target)
{
return Merge(this, target);
}
public NamedContentData ToCleaned()
{
return Clean(this, new NamedContentData());
}
public NamedContentData AddField(string name, ContentFieldData data)
{
Guard.NotNullOrEmpty(name, nameof(name));
this[name] = data;
return this;
}
public IdContentData ToIdModel(Schema schema, bool encodeJsonField)
{
Guard.NotNull(schema, nameof(schema));
var result = new IdContentData();
foreach (var fieldValue in this)
{
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field))
{
continue;
}
var fieldId = field.Id;
if (encodeJsonField && field is JsonField)
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value)
{
if (partitionValue.Value.IsNull())
{
encodedValue[partitionValue.Key] = null;
}
else
{
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString()));
encodedValue[partitionValue.Key] = value;
}
}
result[fieldId] = encodedValue;
}
else
{
result[fieldId] = fieldValue.Value;
}
}
return result;
}
public NamedContentData ToApiModel(Schema schema, LanguagesConfig languagesConfig, bool excludeHidden = true)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languagesConfig, nameof(languagesConfig));
var codeForInvariant = InvariantPartitioning.Instance.Master.Key;
var codeForMasterLanguage = languagesConfig.Master.Language.Iso2Code;
var result = new NamedContentData();
foreach (var fieldValue in this)
{
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field) || (excludeHidden && field.IsHidden))
{
continue;
}
var fieldResult = new ContentFieldData();
var fieldValues = fieldValue.Value;
if (field.Partitioning.Equals(Partitioning.Language))
{
foreach (var languageConfig in languagesConfig)
{
var languageCode = languageConfig.Key;
if (fieldValues.TryGetValue(languageCode, out var value))
{
fieldResult.Add(languageCode, value);
}
else if (languageConfig == languagesConfig.Master && fieldValues.TryGetValue(codeForInvariant, out value))
{
fieldResult.Add(languageCode, value);
}
}
}
else
{
if (fieldValues.TryGetValue(codeForInvariant, out var value))
{
fieldResult.Add(codeForInvariant, value);
}
else if (fieldValues.TryGetValue(codeForMasterLanguage, out value))
{
fieldResult.Add(codeForInvariant, value);
}
else if (fieldValues.Count > 0)
{
fieldResult.Add(codeForInvariant, fieldValues.Values.First());
}
}
result.Add(GetKey(field), fieldResult);
}
return result;
}
public object ToLanguageModel(LanguagesConfig languagesConfig, IReadOnlyCollection<Language> languagePreferences = null)
{
Guard.NotNull(languagesConfig, nameof(languagesConfig));
if (languagePreferences == null || languagePreferences.Count == 0)
{
return this;
}
if (languagePreferences.Count == 1 && languagesConfig.TryGetConfig(languagePreferences.First(), out var languageConfig))
{
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList();
}
var result = new Dictionary<string, JToken>();
foreach (var fieldValue in this)
{
var fieldValues = fieldValue.Value;
foreach (var language in languagePreferences)
{
if (fieldValues.TryGetValue(language, out var value) && value != null)
{
result[fieldValue.Key] = value;
break;
}
}
}
return result;
}
public bool Equals(NamedContentData other)
{
return base.Equals(other);
}
public override string GetKey(Field field)
{
return field.Name;
}
}
}

16
src/Squidex.Domain.Apps.Core/Contents/Status.cs

@ -1,16 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Contents
{
public enum Status
{
Draft,
Archived,
Published
}
}

32
src/Squidex.Domain.Apps.Core/Contents/StatusFlow.cs

@ -1,32 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
namespace Squidex.Domain.Apps.Core.Contents
{
public static class StatusFlow
{
private static readonly Dictionary<Status, Status[]> Flow = new Dictionary<Status, Status[]>
{
[Status.Draft] = new[] { Status.Published, Status.Archived },
[Status.Archived] = new[] { Status.Draft },
[Status.Published] = new[] { Status.Draft, Status.Archived }
};
public static bool Exists(Status status)
{
return Flow.ContainsKey(status);
}
public static bool CanChange(Status status, Status toStatus)
{
return Flow.TryGetValue(status, out var state) && state.Contains(toStatus);
}
}
}

57
src/Squidex.Domain.Apps.Core/FieldExtensions.cs

@ -1,57 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core
{
public static class FieldExtensions
{
public static void AddError(this ConcurrentBag<ValidationError> errors, string message, Field field, IFieldPartitionItem partitionItem = null)
{
AddError(errors, message, !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name, field.Name, partitionItem);
}
public static void AddError(this ConcurrentBag<ValidationError> errors, string message, string fieldName, IFieldPartitionItem partitionItem = null)
{
AddError(errors, message, fieldName, fieldName, partitionItem);
}
public static void AddError(this ConcurrentBag<ValidationError> errors, string message, string displayName, string fieldName, IFieldPartitionItem partitionItem = null)
{
if (partitionItem != null && partitionItem != InvariantPartitioning.Instance.Master)
{
displayName += $" ({partitionItem.Key})";
}
errors.Add(new ValidationError(message.Replace("<FIELD>", displayName), fieldName));
}
public static async Task ValidateAsync(this Field field, JToken value, ValidationContext context, Action<string> addError)
{
try
{
var typedValue = value.IsNull() ? null : field.ConvertValue(value);
foreach (var validator in field.Validators)
{
await validator.ValidateAsync(typedValue, context, addError);
}
}
catch
{
addError("<FIELD> is not a valid value.");
}
}
}
}

22
src/Squidex.Domain.Apps.Core/IFieldPartitionItem.cs

@ -1,22 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core
{
public interface IFieldPartitionItem
{
string Key { get; }
string Name { get; }
bool IsOptional { get; }
IEnumerable<string> Fallback { get; }
}
}

18
src/Squidex.Domain.Apps.Core/IFieldPartitioning.cs

@ -1,18 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core
{
public interface IFieldPartitioning : IReadOnlyCollection<IFieldPartitionItem>
{
IFieldPartitionItem Master { get; }
bool TryGetItem(string key, out IFieldPartitionItem item);
}
}

72
src/Squidex.Domain.Apps.Core/InvariantPartitioning.cs

@ -1,72 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Squidex.Domain.Apps.Core
{
public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem
{
public static readonly InvariantPartitioning Instance = new InvariantPartitioning();
public int Count
{
get { return 1; }
}
public IFieldPartitionItem Master
{
get { return this; }
}
string IFieldPartitionItem.Key
{
get { return "iv"; }
}
string IFieldPartitionItem.Name
{
get { return "Invariant"; }
}
bool IFieldPartitionItem.IsOptional
{
get { return false; }
}
IEnumerable<string> IFieldPartitionItem.Fallback
{
get { return Enumerable.Empty<string>(); }
}
private InvariantPartitioning()
{
}
public bool TryGetItem(string key, out IFieldPartitionItem item)
{
var isFound = string.Equals(key, "iv", StringComparison.OrdinalIgnoreCase);
item = isFound ? this : null;
return isFound;
}
IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator()
{
yield return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
yield return this;
}
}
}

58
src/Squidex.Domain.Apps.Core/LanguageConfig.cs

@ -1,58 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core
{
public sealed class LanguageConfig : IFieldPartitionItem
{
public bool IsOptional { get; }
public Language Language { get; }
public ImmutableList<Language> LanguageFallbacks { get; }
public string Key
{
get { return Language.Iso2Code; }
}
public string Name
{
get { return Language.EnglishName; }
}
IEnumerable<string> IFieldPartitionItem.Fallback
{
get { return LanguageFallbacks.Select(x => x.Iso2Code); }
}
public LanguageConfig(Language language, bool isOptional, params Language[] fallback)
: this(language, isOptional, fallback?.ToImmutableList())
{
}
public LanguageConfig(Language language, bool isOptional, IEnumerable<Language> fallback)
: this(language, isOptional, fallback?.ToImmutableList())
{
}
public LanguageConfig(Language language, bool isOptional = false, ImmutableList<Language> fallback = null)
{
Guard.NotNull(language, nameof(language));
IsOptional = isOptional;
Language = language;
LanguageFallbacks = fallback ?? ImmutableList<Language>.Empty;
}
}
}

218
src/Squidex.Domain.Apps.Core/LanguagesConfig.cs

@ -1,218 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core
{
public sealed class LanguagesConfig : IFieldPartitioning
{
private readonly ImmutableDictionary<Language, LanguageConfig> languages;
private readonly LanguageConfig master;
public static readonly LanguagesConfig Empty = Create();
public LanguageConfig Master
{
get { return master; }
}
public int Count
{
get { return languages.Count; }
}
IFieldPartitionItem IFieldPartitioning.Master
{
get { return Master; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return languages.Values.GetEnumerator();
}
IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator()
{
return languages.Values.GetEnumerator();
}
private LanguagesConfig(ImmutableDictionary<Language, LanguageConfig> languages, LanguageConfig master)
{
this.languages = ValidateLanguages(languages);
this.master = master;
}
public static LanguagesConfig Create(ICollection<LanguageConfig> languageConfigs)
{
Guard.NotNull(languageConfigs, nameof(languageConfigs));
var validated = ValidateLanguages(languageConfigs.ToImmutableDictionary(c => c.Language));
return new LanguagesConfig(validated, languageConfigs.FirstOrDefault());
}
public static LanguagesConfig Create(params Language[] languages)
{
Guard.NotNull(languages, nameof(languages));
var languageConfigs = languages.Select(l => new LanguageConfig(l)).ToList();
return Create(languageConfigs);
}
public LanguagesConfig MakeMaster(Language language)
{
ThrowIfNotFound(language);
return new LanguagesConfig(languages, languages[language]);
}
public LanguagesConfig Add(Language language)
{
ThrowIfFound(language, () => $"Cannot add language '{language.Iso2Code}'.");
var newLanguages = languages.Add(language, new LanguageConfig(language));
return new LanguagesConfig(newLanguages, master ?? newLanguages.Values.First());
}
public LanguagesConfig Update(Language language, bool isOptional, bool isMaster, IEnumerable<Language> fallback)
{
ThrowIfNotFound(language);
if (isOptional)
{
ThrowIfMaster(language, isMaster, () => $"Cannot cannot make language '{language.Iso2Code}' optional");
}
var newLanguage = new LanguageConfig(language, isOptional, fallback);
var newLanguages = ValidateLanguages(languages.SetItem(language, newLanguage));
return new LanguagesConfig(newLanguages, isMaster ? newLanguage : master);
}
public LanguagesConfig Remove(Language language)
{
ThrowIfNotFound(language);
ThrowIfMaster(language, false, () => $"Cannot remove language '{language.Iso2Code}'");
var newLanguages = languages.Remove(language);
foreach (var languageConfig in newLanguages.Values)
{
if (languageConfig.LanguageFallbacks.Contains(language))
{
newLanguages =
newLanguages.SetItem(languageConfig.Language,
new LanguageConfig(
languageConfig.Language,
languageConfig.IsOptional,
languageConfig.LanguageFallbacks.Remove(language)));
}
}
return new LanguagesConfig(newLanguages, master);
}
public bool Contains(Language language)
{
return language != null && languages.ContainsKey(language);
}
public bool TryGetConfig(Language language, out LanguageConfig config)
{
return languages.TryGetValue(language, out config);
}
public bool TryGetItem(string key, out IFieldPartitionItem item)
{
if (Language.IsValidLanguage(key) && languages.TryGetValue(key, out var value))
{
item = value;
return true;
}
item = null;
return false;
}
private static ImmutableDictionary<Language, LanguageConfig> ValidateLanguages(ImmutableDictionary<Language, LanguageConfig> languages)
{
var errors = new List<ValidationError>();
foreach (var languageConfig in languages.Values)
{
foreach (var fallback in languageConfig.LanguageFallbacks)
{
if (!languages.ContainsKey(fallback))
{
var message = $"Config for language '{languageConfig.Language.Iso2Code}' contains unsupported fallback language '{fallback.Iso2Code}'";
errors.Add(new ValidationError(message));
}
}
}
if (errors.Count > 0)
{
throw new ValidationException("Cannot configure language.", errors);
}
return languages;
}
private void ThrowIfNotFound(Language language)
{
if (!Contains(language))
{
throw new DomainObjectNotFoundException(language, "Languages", typeof(LanguagesConfig));
}
}
private void ThrowIfFound(Language language, Func<string> message)
{
if (Contains(language))
{
var error = new ValidationError("Language is already part of the app.", "Language");
throw new ValidationException(message(), error);
}
}
private void ThrowIfMaster(Language language, bool isMaster, Func<string> message)
{
if (master?.Language == language || isMaster)
{
var error = new ValidationError("Language is the master language.", "Language");
throw new ValidationException(message(), error);
}
}
public PartitionResolver ToResolver()
{
return partitioning =>
{
if (partitioning.Equals(Partitioning.Invariant))
{
return InvariantPartitioning.Instance;
}
return this;
};
}
}
}

49
src/Squidex.Domain.Apps.Core/Partitioning.cs

@ -1,49 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core
{
public delegate IFieldPartitioning PartitionResolver(Partitioning key);
public sealed class Partitioning : IEquatable<Partitioning>
{
public static readonly Partitioning Invariant = new Partitioning("invariant");
public static readonly Partitioning Language = new Partitioning("language");
public string Key { get; }
public Partitioning(string key)
{
Guard.NotNullOrEmpty(key, nameof(key));
Key = key;
}
public override bool Equals(object obj)
{
return Equals(obj as Partitioning);
}
public bool Equals(Partitioning other)
{
return string.Equals(other?.Key, Key, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()
{
return Key.GetHashCode();
}
public override string ToString()
{
return Key;
}
}
}

26
src/Squidex.Domain.Apps.Core/PartitioningExtensions.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core
{
public static class PartitioningExtensions
{
private static readonly HashSet<string> AllowedPartitions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
Partitioning.Language.Key,
Partitioning.Invariant.Key
};
public static bool IsValidPartitioning(this string value)
{
return value == null || AllowedPartitions.Contains(value);
}
}
}

79
src/Squidex.Domain.Apps.Core/Schemas/AssetsField.cs

@ -1,79 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class AssetsField : Field<AssetsFieldProperties>, IReferenceField
{
private static readonly ImmutableList<Guid> EmptyIds = ImmutableList<Guid>.Empty;
public AssetsField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new AssetsFieldProperties())
{
}
public AssetsField(long id, string name, Partitioning partitioning, AssetsFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired || Properties.MinItems.HasValue || Properties.MaxItems.HasValue)
{
yield return new CollectionValidator<Guid>(Properties.IsRequired, Properties.MinItems, Properties.MaxItems);
}
yield return new AssetsValidator();
}
public IEnumerable<Guid> GetReferencedIds(JToken value)
{
IEnumerable<Guid> result = null;
try
{
result = value?.ToObject<List<Guid>>();
}
catch
{
result = EmptyIds;
}
return result ?? EmptyIds;
}
public JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds)
{
if (value == null || value.Type == JTokenType.Null)
{
return null;
}
var oldAssetIds = GetReferencedIds(value).ToArray();
var newAssetIds = oldAssetIds.Where(x => !deletedReferencedIds.Contains(x)).ToList();
return newAssetIds.Count != oldAssetIds.Length ? JToken.FromObject(newAssetIds) : value;
}
public override object ConvertValue(JToken value)
{
return value.ToObject<List<Guid>>();
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

57
src/Squidex.Domain.Apps.Core/Schemas/AssetsFieldProperties.cs

@ -1,57 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(AssetsField))]
public sealed class AssetsFieldProperties : FieldProperties
{
private int? minItems;
private int? maxItems;
public int? MinItems
{
get
{
return minItems;
}
set
{
ThrowIfFrozen();
minItems = value;
}
}
public int? MaxItems
{
get
{
return maxItems;
}
set
{
ThrowIfFrozen();
maxItems = value;
}
}
public override JToken GetDefaultValue()
{
return new JArray();
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

44
src/Squidex.Domain.Apps.Core/Schemas/BooleanField.cs

@ -1,44 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class BooleanField : Field<BooleanFieldProperties>
{
public BooleanField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new BooleanFieldProperties())
{
}
public BooleanField(long id, string name, Partitioning partitioning, BooleanFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredValidator();
}
}
public override object ConvertValue(JToken value)
{
return (bool?)value;
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

15
src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldEditor.cs

@ -1,15 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public enum BooleanFieldEditor
{
Checkbox,
Toggle
}
}

57
src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldProperties.cs

@ -1,57 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(BooleanField))]
public sealed class BooleanFieldProperties : FieldProperties
{
private BooleanFieldEditor editor;
private bool? defaultValue;
public bool? DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
defaultValue = value;
}
}
public BooleanFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override JToken GetDefaultValue()
{
return DefaultValue;
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

23
src/Squidex.Domain.Apps.Core/Schemas/CloneableBase.cs

@ -1,23 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class CloneableBase
{
protected T Clone<T>(Action<T> updater) where T : CloneableBase
{
var clone = (T)MemberwiseClone();
updater(clone);
return clone;
}
}
}

15
src/Squidex.Domain.Apps.Core/Schemas/DateTimeCalculatedDefaultValue.cs

@ -1,15 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public enum DateTimeCalculatedDefaultValue
{
Now,
Today
}
}

64
src/Squidex.Domain.Apps.Core/Schemas/DateTimeField.cs

@ -1,64 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NodaTime;
using NodaTime.Text;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class DateTimeField : Field<DateTimeFieldProperties>
{
public DateTimeField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new DateTimeFieldProperties())
{
}
public DateTimeField(long id, string name, Partitioning partitioning, DateTimeFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredValidator();
}
if (Properties.MinValue.HasValue || Properties.MaxValue.HasValue)
{
yield return new RangeValidator<Instant>(Properties.MinValue, Properties.MaxValue);
}
}
public override object ConvertValue(JToken value)
{
if (value.Type == JTokenType.String)
{
var parseResult = InstantPattern.General.Parse(value.ToString());
if (!parseResult.Success)
{
throw parseResult.Exception;
}
return parseResult.Value;
}
throw new InvalidCastException("Invalid json type, expected string.");
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

15
src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldEditor.cs

@ -1,15 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public enum DateTimeFieldEditor
{
Date,
DateTime
}
}

115
src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldProperties.cs

@ -1,115 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(DateTimeField))]
public sealed class DateTimeFieldProperties : FieldProperties
{
private DateTimeFieldEditor editor;
private DateTimeCalculatedDefaultValue? calculatedDefaultValue;
private Instant? maxValue;
private Instant? minValue;
private Instant? defaultValue;
public Instant? MaxValue
{
get
{
return maxValue;
}
set
{
ThrowIfFrozen();
maxValue = value;
}
}
public Instant? MinValue
{
get
{
return minValue;
}
set
{
ThrowIfFrozen();
minValue = value;
}
}
public Instant? DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
defaultValue = value;
}
}
public DateTimeCalculatedDefaultValue? CalculatedDefaultValue
{
get
{
return calculatedDefaultValue;
}
set
{
ThrowIfFrozen();
calculatedDefaultValue = value;
}
}
public DateTimeFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override JToken GetDefaultValue()
{
if (CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now)
{
return DateTime.UtcNow.ToString("o");
}
else if (CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today)
{
return DateTime.UtcNow.Date.ToString("o");
}
else
{
return DefaultValue?.ToString();
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

59
src/Squidex.Domain.Apps.Core/Schemas/Edm/EdmSchemaExtensions.cs

@ -1,59 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.Edm;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Edm
{
public static class EdmSchemaExtensions
{
public static string EscapeEdmField(this string field)
{
return field.Replace("-", "_");
}
public static string UnescapeEdmField(this string field)
{
return field.Replace("_", "-");
}
public static EdmComplexType BuildEdmType(this Schema schema, PartitionResolver partitionResolver, Func<EdmComplexType, EdmComplexType> typeResolver)
{
Guard.NotNull(typeResolver, nameof(typeResolver));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
var schemaName = schema.Name.ToPascalCase();
var edmType = new EdmComplexType("Squidex", schemaName);
foreach (var field in schema.FieldsByName.Values.Where(x => !x.IsHidden))
{
var edmValueType = EdmTypeVisitor.CreateEdmType(field);
if (edmValueType == null)
{
continue;
}
var partitionType = typeResolver(new EdmComplexType("Squidex", $"{schemaName}{field.Name.ToPascalCase()}Property"));
var partition = partitionResolver(field.Partitioning);
foreach (var partitionItem in partition)
{
partitionType.AddStructuralProperty(partitionItem.Key, edmValueType);
}
edmType.AddStructuralProperty(field.Name.EscapeEdmField(), new EdmComplexTypeReference(partitionType, false));
}
return edmType;
}
}
}

75
src/Squidex.Domain.Apps.Core/Schemas/Edm/EdmTypeVisitor.cs

@ -1,75 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.OData.Edm;
namespace Squidex.Domain.Apps.Core.Schemas.Edm
{
public sealed class EdmTypeVisitor : IFieldVisitor<IEdmTypeReference>
{
private static readonly EdmTypeVisitor Instance = new EdmTypeVisitor();
private EdmTypeVisitor()
{
}
public static IEdmTypeReference CreateEdmType(Field field)
{
return field.Accept(Instance);
}
public IEdmTypeReference Visit(AssetsField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.String, field);
}
public IEdmTypeReference Visit(BooleanField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.Boolean, field);
}
public IEdmTypeReference Visit(DateTimeField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.DateTimeOffset, field);
}
public IEdmTypeReference Visit(GeolocationField field)
{
return null;
}
public IEdmTypeReference Visit(JsonField field)
{
return null;
}
public IEdmTypeReference Visit(NumberField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.Double, field);
}
public IEdmTypeReference Visit(ReferencesField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.String, field);
}
public IEdmTypeReference Visit(StringField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.String, field);
}
public IEdmTypeReference Visit(TagsField field)
{
return CreatePrimitive(EdmPrimitiveTypeKind.String, field);
}
private static IEdmTypeReference CreatePrimitive(EdmPrimitiveTypeKind kind, Field field)
{
return EdmCoreModel.Instance.GetPrimitive(kind, !field.RawProperties.IsRequired);
}
}
}

130
src/Squidex.Domain.Apps.Core/Schemas/Field.cs

@ -1,130 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class Field : CloneableBase
{
private readonly Lazy<List<IValidator>> validators;
private readonly long fieldId;
private readonly Partitioning partitioning;
private readonly string fieldName;
private bool isDisabled;
private bool isHidden;
private bool isLocked;
public long Id
{
get { return fieldId; }
}
public string Name
{
get { return fieldName; }
}
public bool IsLocked
{
get { return isLocked; }
}
public bool IsHidden
{
get { return isHidden; }
}
public bool IsDisabled
{
get { return isDisabled; }
}
public Partitioning Partitioning
{
get { return partitioning; }
}
public IReadOnlyList<IValidator> Validators
{
get { return validators.Value; }
}
public abstract FieldProperties RawProperties { get; }
protected Field(long id, string name, Partitioning partitioning)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(partitioning, nameof(partitioning));
Guard.GreaterThan(id, 0, nameof(id));
fieldId = id;
fieldName = name;
this.partitioning = partitioning;
validators = new Lazy<List<IValidator>>(() => new List<IValidator>(CreateValidators()));
}
protected abstract Field UpdateInternal(FieldProperties newProperties);
protected abstract IEnumerable<IValidator> CreateValidators();
public abstract object ConvertValue(JToken value);
public Field Lock()
{
return Clone<Field>(clone =>
{
clone.isLocked = true;
});
}
public Field Hide()
{
return Clone<Field>(clone =>
{
clone.isHidden = true;
});
}
public Field Show()
{
return Clone<Field>(clone =>
{
clone.isHidden = false;
});
}
public Field Disable()
{
return Clone<Field>(clone =>
{
clone.isDisabled = true;
});
}
public Field Enable()
{
return Clone<Field>(clone =>
{
clone.isDisabled = false;
});
}
public Field Update(FieldProperties newProperties)
{
return UpdateInternal(newProperties);
}
public abstract T Accept<T>(IFieldVisitor<T> visitor);
}
}

70
src/Squidex.Domain.Apps.Core/Schemas/FieldProperties.cs

@ -1,70 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class FieldProperties : NamedElementPropertiesBase
{
private bool isRequired;
private bool isListField;
private string placeholder;
public bool IsRequired
{
get
{
return isRequired;
}
set
{
ThrowIfFrozen();
isRequired = value;
}
}
public bool IsListField
{
get
{
return isListField;
}
set
{
ThrowIfFrozen();
isListField = value;
}
}
public string Placeholder
{
get
{
return placeholder;
}
set
{
ThrowIfFrozen();
placeholder = value;
}
}
public abstract JToken GetDefaultValue();
public abstract T Accept<T>(IFieldPropertiesVisitor<T> visitor);
public virtual bool ShouldApplyDefaultValue(JToken value)
{
return value.IsNull();
}
}
}

115
src/Squidex.Domain.Apps.Core/Schemas/FieldRegistry.cs

@ -1,115 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class FieldRegistry
{
private delegate Field FactoryFunction(long id, string name, Partitioning partitioning, FieldProperties properties);
private readonly TypeNameRegistry typeNameRegistry;
private readonly Dictionary<Type, Registered> fieldsByPropertyType = new Dictionary<Type, Registered>();
private sealed class Registered
{
private readonly FactoryFunction fieldFactory;
private readonly Type propertiesType;
public Type PropertiesType
{
get { return propertiesType; }
}
public Registered(FactoryFunction fieldFactory, Type propertiesType)
{
this.fieldFactory = fieldFactory;
this.propertiesType = propertiesType;
}
public Field CreateField(long id, string name, Partitioning partitioning, FieldProperties properties)
{
return fieldFactory(id, name, partitioning, properties);
}
}
public FieldRegistry(TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
this.typeNameRegistry = typeNameRegistry;
Add<BooleanFieldProperties>(
(id, name, partitioning, properties) =>
new BooleanField(id, name, partitioning, (BooleanFieldProperties)properties));
Add<NumberFieldProperties>(
(id, name, partitioning, properties) =>
new NumberField(id, name, partitioning, (NumberFieldProperties)properties));
Add<StringFieldProperties>(
(id, name, partitioning, properties) =>
new StringField(id, name, partitioning, (StringFieldProperties)properties));
Add<JsonFieldProperties>(
(id, name, partitioning, properties) =>
new JsonField(id, name, partitioning, (JsonFieldProperties)properties));
Add<AssetsFieldProperties>(
(id, name, partitioning, properties) =>
new AssetsField(id, name, partitioning, (AssetsFieldProperties)properties));
Add<GeolocationFieldProperties>(
(id, name, partitioning, properties) =>
new GeolocationField(id, name, partitioning, (GeolocationFieldProperties)properties));
Add<ReferencesFieldProperties>(
(id, name, partitioning, properties) =>
new ReferencesField(id, name, partitioning, (ReferencesFieldProperties)properties));
Add<DateTimeFieldProperties>(
(id, name, partitioning, properties) =>
new DateTimeField(id, name, partitioning, (DateTimeFieldProperties)properties));
Add<TagsFieldProperties>(
(id, name, partitioning, properties) =>
new TagsField(id, name, partitioning, (TagsFieldProperties)properties));
typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "DateTime");
typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "References");
}
private void Add<TFieldProperties>(FactoryFunction fieldFactory)
{
Guard.NotNull(fieldFactory, nameof(fieldFactory));
typeNameRegistry.Map(typeof(TFieldProperties));
var registered = new Registered(fieldFactory, typeof(TFieldProperties));
fieldsByPropertyType[registered.PropertiesType] = registered;
}
public Field CreateField(long id, string name, Partitioning partitioning, FieldProperties properties)
{
Guard.NotNull(properties, nameof(properties));
var registered = fieldsByPropertyType.GetOrDefault(properties.GetType());
if (registered == null)
{
throw new InvalidOperationException($"The field property '{properties.GetType()}' is not supported.");
}
return registered.CreateField(id, name, partitioning, properties);
}
}
}

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

@ -1,55 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class Field<T> : Field where T : FieldProperties
{
private T properties;
public T Properties
{
get { return properties; }
}
public override FieldProperties RawProperties
{
get { return properties; }
}
protected Field(long id, string name, Partitioning partitioning, T properties)
: base(id, name, partitioning)
{
Guard.NotNull(properties, nameof(properties));
this.properties = ValidateProperties(properties);
}
protected override Field UpdateInternal(FieldProperties newProperties)
{
var typedProperties = ValidateProperties(newProperties);
return Clone<Field<T>>(clone => clone.properties = typedProperties);
}
private T ValidateProperties(FieldProperties newProperties)
{
Guard.NotNull(newProperties, nameof(newProperties));
newProperties.Freeze();
if (!(newProperties is T typedProperties))
{
throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties));
}
return typedProperties;
}
}
}

63
src/Squidex.Domain.Apps.Core/Schemas/GeolocationField.cs

@ -1,63 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class GeolocationField : Field<GeolocationFieldProperties>
{
public GeolocationField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new GeolocationFieldProperties())
{
}
public GeolocationField(long id, string name, Partitioning partitioning, GeolocationFieldProperties properties)
: base(id, name, partitioning, 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;
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

14
src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldEditor.cs

@ -1,14 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public enum GeolocationFieldEditor
{
Map
}
}

42
src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldProperties.cs

@ -1,42 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(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;
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

30
src/Squidex.Domain.Apps.Core/Schemas/IFieldPropertiesVisitor.cs

@ -1,30 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public interface IFieldPropertiesVisitor<out T>
{
T Visit(AssetsFieldProperties properties);
T Visit(BooleanFieldProperties properties);
T Visit(DateTimeFieldProperties properties);
T Visit(GeolocationFieldProperties properties);
T Visit(JsonFieldProperties properties);
T Visit(NumberFieldProperties properties);
T Visit(ReferencesFieldProperties properties);
T Visit(StringFieldProperties properties);
T Visit(TagsFieldProperties properties);
}
}

30
src/Squidex.Domain.Apps.Core/Schemas/IFieldVisitor.cs

@ -1,30 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public interface IFieldVisitor<out T>
{
T Visit(AssetsField field);
T Visit(BooleanField field);
T Visit(DateTimeField field);
T Visit(GeolocationField field);
T Visit(JsonField field);
T Visit(NumberField field);
T Visit(ReferencesField field);
T Visit(StringField field);
T Visit(TagsField field);
}
}

20
src/Squidex.Domain.Apps.Core/Schemas/IReferenceField.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Core.Schemas
{
public interface IReferenceField
{
IEnumerable<Guid> GetReferencedIds(JToken value);
JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds);
}
}

35
src/Squidex.Domain.Apps.Core/Schemas/Json/JsonFieldModel.cs

@ -1,35 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonFieldModel
{
[JsonProperty]
public long Id { get; set; }
[JsonProperty]
public bool IsHidden { get; set; }
[JsonProperty]
public bool IsLocked { get; set; }
[JsonProperty]
public bool IsDisabled { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public string Partitioning { get; set; }
[JsonProperty]
public FieldProperties Properties { get; set; }
}
}

86
src/Squidex.Domain.Apps.Core/Schemas/Json/JsonSchemaModel.cs

@ -1,86 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonSchemaModel
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public bool IsPublished { get; set; }
[JsonProperty]
public SchemaProperties Properties { get; set; }
[JsonProperty]
public List<JsonFieldModel> Fields { get; set; }
public JsonSchemaModel()
{
}
public JsonSchemaModel(Schema schema)
{
Name = schema.Name;
Properties = schema.Properties;
Fields =
schema.Fields?.Select(x =>
new JsonFieldModel
{
Id = x.Id,
Name = x.Name,
IsHidden = x.IsHidden,
IsLocked = x.IsLocked,
IsDisabled = x.IsDisabled,
Partitioning = x.Partitioning.Key,
Properties = x.RawProperties
}).ToList();
IsPublished = schema.IsPublished;
}
public Schema ToSchema(FieldRegistry fieldRegistry)
{
var fields = Fields?.Select(fieldModel =>
{
var parititonKey = new Partitioning(fieldModel.Partitioning);
var field = fieldRegistry.CreateField(fieldModel.Id, fieldModel.Name, parititonKey, fieldModel.Properties);
if (fieldModel.IsDisabled)
{
field = field.Disable();
}
if (fieldModel.IsLocked)
{
field = field.Lock();
}
if (fieldModel.IsHidden)
{
field = field.Hide();
}
return field;
}).ToImmutableList() ?? ImmutableList<Field>.Empty;
var schema = new Schema(Name, IsPublished, Properties, fields);
return schema;
}
}
}

40
src/Squidex.Domain.Apps.Core/Schemas/Json/SchemaConverter.cs

@ -1,40 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class SchemaConverter : JsonConverter
{
private readonly FieldRegistry fieldRegistry;
public SchemaConverter(FieldRegistry fieldRegistry)
{
Guard.NotNull(fieldRegistry, nameof(fieldRegistry));
this.fieldRegistry = fieldRegistry;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, new JsonSchemaModel((Schema)value));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<JsonSchemaModel>(reader).ToSchema(fieldRegistry);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Schema);
}
}
}

44
src/Squidex.Domain.Apps.Core/Schemas/JsonField.cs

@ -1,44 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class JsonField : Field<JsonFieldProperties>
{
public JsonField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new JsonFieldProperties())
{
}
public JsonField(long id, string name, Partitioning partitioning, JsonFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredValidator();
}
}
public override object ConvertValue(JToken value)
{
return value;
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

26
src/Squidex.Domain.Apps.Core/Schemas/JsonFieldProperties.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(JsonField))]
public sealed class JsonFieldProperties : FieldProperties
{
public override JToken GetDefaultValue()
{
return JValue.CreateNull();
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

55
src/Squidex.Domain.Apps.Core/Schemas/JsonSchema/ContentSchemaBuilder.cs

@ -1,55 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NJsonSchema;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.JsonSchema
{
public sealed class ContentSchemaBuilder
{
public JsonSchema4 CreateContentSchema(Schema schema, JsonSchema4 dataSchema)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(dataSchema, nameof(dataSchema));
var schemaName = schema.Properties.Label.WithFallback(schema.Name);
var contentSchema = new JsonSchema4
{
Properties =
{
["id"] = CreateProperty($"The id of the {schemaName} content."),
["data"] = CreateProperty($"The data of the {schemaName}.", dataSchema),
["version"] = CreateProperty($"The version of the {schemaName}.", JsonObjectType.Number),
["created"] = CreateProperty($"The date and time when the {schemaName} content has been created.", "date-time"),
["createdBy"] = CreateProperty($"The user that has created the {schemaName} content."),
["lastModified"] = CreateProperty($"The date and time when the {schemaName} content has been modified last.", "date-time"),
["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content last.")
},
Type = JsonObjectType.Object
};
return contentSchema;
}
private static JsonProperty CreateProperty(string description, JsonSchema4 dataSchema)
{
return new JsonProperty { Description = description, IsRequired = true, Type = JsonObjectType.Object, Reference = dataSchema };
}
private static JsonProperty CreateProperty(string description, JsonObjectType type)
{
return new JsonProperty { Description = description, IsRequired = true, Type = type };
}
private static JsonProperty CreateProperty(string description, string format = null)
{
return new JsonProperty { Description = description, Format = format, IsRequired = true, Type = JsonObjectType.String };
}
}
}

70
src/Squidex.Domain.Apps.Core/Schemas/JsonSchema/JsonSchemaExtensions.cs

@ -1,70 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using NJsonSchema;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.JsonSchema
{
public static class JsonSchemaExtensions
{
public static JsonSchema4 BuildJsonSchema(this Schema schema, PartitionResolver partitionResolver, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotNull(schemaResolver, nameof(schemaResolver));
Guard.NotNull(partitionResolver, nameof(partitionResolver));
var schemaName = schema.Name.ToPascalCase();
var jsonTypeVisitor = new JsonTypeVisitor(schemaResolver);
var jsonSchema = new JsonSchema4 { Type = JsonObjectType.Object };
foreach (var field in schema.Fields.Where(x => !x.IsHidden))
{
var partitionProperty = CreateProperty(field);
var partitionObject = new JsonSchema4 { Type = JsonObjectType.Object, AllowAdditionalProperties = false };
var partition = partitionResolver(field.Partitioning);
foreach (var partitionItem in partition)
{
var partitionItemProperty = field.Accept(jsonTypeVisitor);
partitionItemProperty.Description = partitionItem.Name;
partitionObject.Properties.Add(partitionItem.Key, partitionItemProperty);
}
partitionProperty.Reference = schemaResolver($"{schemaName}{field.Name.ToPascalCase()}Property", partitionObject);
jsonSchema.Properties.Add(field.Name, partitionProperty);
}
return jsonSchema;
}
public static JsonProperty CreateProperty(Field field)
{
var jsonProperty = new JsonProperty { IsRequired = field.RawProperties.IsRequired, Type = JsonObjectType.Object };
if (!string.IsNullOrWhiteSpace(field.RawProperties.Hints))
{
jsonProperty.Description = field.RawProperties.Hints;
}
else
{
jsonProperty.Description = field.Name;
}
if (!string.IsNullOrWhiteSpace(field.RawProperties.Hints))
{
jsonProperty.Description += $" ({field.RawProperties.Hints}).";
}
return jsonProperty;
}
}
}

161
src/Squidex.Domain.Apps.Core/Schemas/JsonSchema/JsonTypeVisitor.cs

@ -1,161 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.ObjectModel;
using NJsonSchema;
namespace Squidex.Domain.Apps.Core.Schemas.JsonSchema
{
public sealed class JsonTypeVisitor : IFieldVisitor<JsonProperty>
{
private readonly Func<string, JsonSchema4, JsonSchema4> schemaResolver;
public JsonTypeVisitor(Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
this.schemaResolver = schemaResolver;
}
public JsonProperty Visit(AssetsField field)
{
return CreateProperty(field, jsonProperty =>
{
var itemSchema = schemaResolver("AssetItem", new JsonSchema4 { Type = JsonObjectType.String });
jsonProperty.Type = JsonObjectType.Array;
jsonProperty.Item = itemSchema;
});
}
public JsonProperty Visit(BooleanField field)
{
return CreateProperty(field, jsonProperty =>
{
jsonProperty.Type = JsonObjectType.Boolean;
});
}
public JsonProperty Visit(DateTimeField field)
{
return CreateProperty(field, jsonProperty =>
{
jsonProperty.Type = JsonObjectType.String;
jsonProperty.Format = JsonFormatStrings.DateTime;
});
}
public JsonProperty Visit(GeolocationField field)
{
return CreateProperty(field, jsonProperty =>
{
var geolocationSchema = new JsonSchema4
{
AllowAdditionalProperties = false
};
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
});
var schemaReference = schemaResolver("GeolocationDto", geolocationSchema);
jsonProperty.Type = JsonObjectType.Object;
jsonProperty.Reference = schemaReference;
});
}
public JsonProperty Visit(JsonField field)
{
return CreateProperty(field, jsonProperty =>
{
jsonProperty.Type = JsonObjectType.Object;
});
}
public JsonProperty Visit(NumberField field)
{
return CreateProperty(field, jsonProperty =>
{
jsonProperty.Type = JsonObjectType.Number;
if (field.Properties.MinValue.HasValue)
{
jsonProperty.Minimum = (decimal)field.Properties.MinValue.Value;
}
if (field.Properties.MaxValue.HasValue)
{
jsonProperty.Maximum = (decimal)field.Properties.MaxValue.Value;
}
});
}
public JsonProperty Visit(ReferencesField field)
{
return CreateProperty(field, jsonProperty =>
{
var itemSchema = schemaResolver("ReferenceItem", new JsonSchema4 { Type = JsonObjectType.String });
jsonProperty.Type = JsonObjectType.Array;
jsonProperty.Item = itemSchema;
});
}
public JsonProperty Visit(StringField field)
{
return CreateProperty(field, jsonProperty =>
{
jsonProperty.Type = JsonObjectType.String;
jsonProperty.MinLength = field.Properties.MinLength;
jsonProperty.MaxLength = field.Properties.MaxLength;
if (field.Properties.AllowedValues != null)
{
var names = jsonProperty.EnumerationNames = jsonProperty.EnumerationNames ?? new Collection<string>();
foreach (var value in field.Properties.AllowedValues)
{
names.Add(value);
}
}
});
}
public JsonProperty Visit(TagsField field)
{
return CreateProperty(field, jsonProperty =>
{
var itemSchema = schemaResolver("TagsItem", new JsonSchema4 { Type = JsonObjectType.String });
jsonProperty.Type = JsonObjectType.Array;
jsonProperty.Item = itemSchema;
});
}
private static JsonProperty CreateProperty(Field field, Action<JsonProperty> updater)
{
var property = new JsonProperty { IsRequired = field.RawProperties.IsRequired };
updater(property);
return property;
}
}
}

60
src/Squidex.Domain.Apps.Core/Schemas/NamedElementPropertiesBase.cs

@ -1,60 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.Schemas
{
public abstract class NamedElementPropertiesBase
{
private string label;
private string hints;
protected bool IsFrozen { get; private set; }
public string Label
{
get
{
return label;
}
set
{
ThrowIfFrozen();
label = value;
}
}
public string Hints
{
get
{
return hints;
}
set
{
ThrowIfFrozen();
hints = value;
}
}
protected void ThrowIfFrozen()
{
if (IsFrozen)
{
throw new InvalidOperationException("Object is frozen.");
}
}
public void Freeze()
{
IsFrozen = true;
}
}
}

55
src/Squidex.Domain.Apps.Core/Schemas/NumberField.cs

@ -1,55 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class NumberField : Field<NumberFieldProperties>
{
public NumberField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new NumberFieldProperties())
{
}
public NumberField(long id, string name, Partitioning partitioning, NumberFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredValidator();
}
if (Properties.MinValue.HasValue || Properties.MaxValue.HasValue)
{
yield return new RangeValidator<double>(Properties.MinValue, Properties.MaxValue);
}
if (Properties.AllowedValues != null)
{
yield return new AllowedValuesValidator<double>(Properties.AllowedValues.ToArray());
}
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
public override object ConvertValue(JToken value)
{
return (double?)value;
}
}
}

17
src/Squidex.Domain.Apps.Core/Schemas/NumberFieldEditor.cs

@ -1,17 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public enum NumberFieldEditor
{
Input,
Radio,
Dropdown,
Stars
}
}

103
src/Squidex.Domain.Apps.Core/Schemas/NumberFieldProperties.cs

@ -1,103 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(NumberField))]
public sealed class NumberFieldProperties : FieldProperties
{
private double? maxValue;
private double? minValue;
private double? defaultValue;
private ImmutableList<double> allowedValues;
private NumberFieldEditor editor;
public double? MaxValue
{
get
{
return maxValue;
}
set
{
ThrowIfFrozen();
maxValue = value;
}
}
public double? MinValue
{
get
{
return minValue;
}
set
{
ThrowIfFrozen();
minValue = value;
}
}
public double? DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
defaultValue = value;
}
}
public ImmutableList<double> AllowedValues
{
get
{
return allowedValues;
}
set
{
ThrowIfFrozen();
allowedValues = value;
}
}
public NumberFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override JToken GetDefaultValue()
{
return DefaultValue;
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

87
src/Squidex.Domain.Apps.Core/Schemas/ReferencesField.cs

@ -1,87 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class ReferencesField : Field<ReferencesFieldProperties>, IReferenceField
{
private static readonly ImmutableList<Guid> EmptyIds = ImmutableList<Guid>.Empty;
public ReferencesField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new ReferencesFieldProperties())
{
}
public ReferencesField(long id, string name, Partitioning partitioning, ReferencesFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired || Properties.MinItems.HasValue || Properties.MaxItems.HasValue)
{
yield return new CollectionValidator<Guid>(Properties.IsRequired, Properties.MinItems, Properties.MaxItems);
}
if (Properties.SchemaId != Guid.Empty)
{
yield return new ReferencesValidator(Properties.SchemaId);
}
}
public IEnumerable<Guid> GetReferencedIds(JToken value)
{
IEnumerable<Guid> result = null;
try
{
result = value?.ToObject<List<Guid>>();
}
catch
{
result = EmptyIds;
}
return (result ?? EmptyIds).Union(new[] { Properties.SchemaId });
}
public JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds)
{
if (value == null || value.Type == JTokenType.Null)
{
return null;
}
if (deletedReferencedIds.Contains(Properties.SchemaId))
{
return new JArray();
}
var oldReferenceIds = GetReferencedIds(value).TakeWhile(x => x != Properties.SchemaId).ToArray();
var newReferenceIds = oldReferenceIds.Where(x => !deletedReferencedIds.Contains(x)).ToList();
return newReferenceIds.Count != oldReferenceIds.Length ? JToken.FromObject(newReferenceIds) : value;
}
public override object ConvertValue(JToken value)
{
return value.ToObject<List<Guid>>();
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

73
src/Squidex.Domain.Apps.Core/Schemas/ReferencesFieldProperties.cs

@ -1,73 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(ReferencesField))]
public sealed class ReferencesFieldProperties : FieldProperties
{
private int? minItems;
private int? maxItems;
private Guid schemaId;
public int? MinItems
{
get
{
return minItems;
}
set
{
ThrowIfFrozen();
minItems = value;
}
}
public int? MaxItems
{
get
{
return maxItems;
}
set
{
ThrowIfFrozen();
maxItems = value;
}
}
public Guid SchemaId
{
get
{
return schemaId;
}
set
{
ThrowIfFrozen();
schemaId = value;
}
}
public override JToken GetDefaultValue()
{
return new JArray();
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

170
src/Squidex.Domain.Apps.Core/Schemas/Schema.cs

@ -1,170 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class Schema
{
private readonly string name;
private readonly SchemaProperties properties;
private readonly ImmutableList<Field> fields;
private readonly ImmutableDictionary<long, Field> fieldsById;
private readonly ImmutableDictionary<string, Field> fieldsByName;
private readonly bool isPublished;
public string Name
{
get { return name; }
}
public bool IsPublished
{
get { return isPublished; }
}
public ImmutableList<Field> Fields
{
get { return fields; }
}
public ImmutableDictionary<long, Field> FieldsById
{
get { return fieldsById; }
}
public ImmutableDictionary<string, Field> FieldsByName
{
get { return fieldsByName; }
}
public SchemaProperties Properties
{
get { return properties; }
}
public Schema(string name, bool isPublished, SchemaProperties properties, ImmutableList<Field> fields)
{
Guard.NotNull(fields, nameof(fields));
Guard.NotNull(properties, nameof(properties));
Guard.NotNullOrEmpty(name, nameof(name));
fieldsById = fields.ToImmutableDictionary(x => x.Id);
fieldsByName = fields.ToImmutableDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
this.name = name;
this.fields = fields;
this.properties = properties;
this.properties.Freeze();
this.isPublished = isPublished;
}
public static Schema Create(string name, SchemaProperties newProperties)
{
return new Schema(name, false, newProperties, ImmutableList<Field>.Empty);
}
public Schema Update(SchemaProperties newProperties)
{
Guard.NotNull(newProperties, nameof(newProperties));
return new Schema(name, isPublished, newProperties, fields);
}
public Schema UpdateField(long fieldId, FieldProperties newProperties)
{
return UpdateField(fieldId, field => field.Update(newProperties));
}
public Schema LockField(long fieldId)
{
return UpdateField(fieldId, field => field.Lock());
}
public Schema DisableField(long fieldId)
{
return UpdateField(fieldId, field => field.Disable());
}
public Schema EnableField(long fieldId)
{
return UpdateField(fieldId, field => field.Enable());
}
public Schema HideField(long fieldId)
{
return UpdateField(fieldId, field => field.Hide());
}
public Schema ShowField(long fieldId)
{
return UpdateField(fieldId, field => field.Show());
}
public Schema Publish()
{
return new Schema(name, true, properties, fields);
}
public Schema Unpublish()
{
return new Schema(name, false, properties, fields);
}
public Schema DeleteField(long fieldId)
{
var newFields = fields.Where(x => x.Id != fieldId).ToImmutableList();
return new Schema(name, isPublished, properties, newFields);
}
public Schema UpdateField(long fieldId, Func<Field, Field> updater)
{
Guard.NotNull(updater, nameof(updater));
var newFields = fields.Select(f => f.Id == fieldId ? updater(f) ?? f : f).ToImmutableList();
return new Schema(name, isPublished, properties, newFields);
}
public Schema ReorderFields(List<long> ids)
{
Guard.NotNull(ids, nameof(ids));
if (ids.Count != fields.Count || ids.Any(x => !fieldsById.ContainsKey(x)))
{
throw new ArgumentException("Ids must cover all fields.", nameof(ids));
}
var newFields = fields.OrderBy(f => ids.IndexOf(f.Id)).ToImmutableList();
return new Schema(name, isPublished, properties, newFields);
}
public Schema AddField(Field field)
{
Guard.NotNull(field, nameof(field));
if (fieldsByName.ContainsKey(field.Name) || fieldsById.ContainsKey(field.Id))
{
throw new ArgumentException($"A field with name '{field.Name}' already exists.", nameof(field));
}
var newFields = fields.Add(field);
return new Schema(name, isPublished, properties, newFields);
}
}
}

13
src/Squidex.Domain.Apps.Core/Schemas/SchemaProperties.cs

@ -1,13 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class SchemaProperties : NamedElementPropertiesBase
{
}
}

60
src/Squidex.Domain.Apps.Core/Schemas/StringField.cs

@ -1,60 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class StringField : Field<StringFieldProperties>
{
public StringField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new StringFieldProperties())
{
}
public StringField(long id, string name, Partitioning partitioning, StringFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredStringValidator();
}
if (Properties.MinLength.HasValue || Properties.MaxLength.HasValue)
{
yield return new StringLengthValidator(Properties.MinLength, Properties.MaxLength);
}
if (!string.IsNullOrWhiteSpace(Properties.Pattern))
{
yield return new PatternValidator(Properties.Pattern, Properties.PatternMessage);
}
if (Properties.AllowedValues != null)
{
yield return new AllowedValuesValidator<string>(Properties.AllowedValues.ToArray());
}
}
public override object ConvertValue(JToken value)
{
return value.ToString();
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

19
src/Squidex.Domain.Apps.Core/Schemas/StringFieldEditor.cs

@ -1,19 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas
{
public enum StringFieldEditor
{
Input,
Markdown,
Dropdown,
Radio,
RichText,
TextArea
}
}

139
src/Squidex.Domain.Apps.Core/Schemas/StringFieldProperties.cs

@ -1,139 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(StringField))]
public sealed class StringFieldProperties : FieldProperties
{
private int? minLength;
private int? maxLength;
private string pattern;
private string patternMessage;
private string defaultValue;
private ImmutableList<string> allowedValues;
private StringFieldEditor editor;
public int? MinLength
{
get
{
return minLength;
}
set
{
ThrowIfFrozen();
minLength = value;
}
}
public int? MaxLength
{
get
{
return maxLength;
}
set
{
ThrowIfFrozen();
maxLength = value;
}
}
public string DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
defaultValue = value;
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
ThrowIfFrozen();
pattern = value;
}
}
public string PatternMessage
{
get
{
return patternMessage;
}
set
{
ThrowIfFrozen();
patternMessage = value;
}
}
public ImmutableList<string> AllowedValues
{
get
{
return allowedValues;
}
set
{
ThrowIfFrozen();
allowedValues = value;
}
}
public StringFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override JToken GetDefaultValue()
{
return DefaultValue;
}
public override bool ShouldApplyDefaultValue(JToken value)
{
return value.IsNull() || (value is JValue jValue && Equals(jValue.Value, string.Empty));
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

49
src/Squidex.Domain.Apps.Core/Schemas/TagsField.cs

@ -1,49 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas.Validators;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class TagsField : Field<TagsFieldProperties>
{
private static readonly ImmutableList<string> EmptyTags = ImmutableList<string>.Empty;
public TagsField(long id, string name, Partitioning partitioning)
: this(id, name, partitioning, new TagsFieldProperties())
{
}
public TagsField(long id, string name, Partitioning partitioning, TagsFieldProperties properties)
: base(id, name, partitioning, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired || Properties.MinItems.HasValue || Properties.MaxItems.HasValue)
{
yield return new CollectionValidator<string>(Properties.IsRequired, Properties.MinItems, Properties.MaxItems);
}
yield return new CollectionItemValidator<string>(new RequiredStringValidator());
}
public override object ConvertValue(JToken value)
{
return value.ToObject<List<string>>();
}
public override T Accept<T>(IFieldVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

57
src/Squidex.Domain.Apps.Core/Schemas/TagsFieldProperties.cs

@ -1,57 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName(nameof(TagsField))]
public sealed class TagsFieldProperties : FieldProperties
{
private int? minItems;
private int? maxItems;
public int? MinItems
{
get
{
return minItems;
}
set
{
ThrowIfFrozen();
minItems = value;
}
}
public int? MaxItems
{
get
{
return maxItems;
}
set
{
ThrowIfFrozen();
maxItems = value;
}
}
public override JToken GetDefaultValue()
{
return new JArray();
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

58
src/Squidex.Domain.Apps.Core/Schemas/ValidationContext.cs

@ -1,58 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas
{
public sealed class ValidationContext
{
private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent;
private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset;
public bool IsOptional { get; }
public ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset)
: this(checkContent, checkAsset, false)
{
}
private ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset,
bool isOptional)
{
Guard.NotNull(checkAsset, nameof(checkAsset));
Guard.NotNull(checkContent, nameof(checkAsset));
this.checkContent = checkContent;
this.checkAsset = checkAsset;
IsOptional = isOptional;
}
public ValidationContext Optional(bool isOptional)
{
return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, isOptional);
}
public Task<IReadOnlyList<Guid>> GetInvalidContentIdsAsync(IEnumerable<Guid> contentIds, Guid schemaId)
{
return checkContent(contentIds, schemaId);
}
public Task<IReadOnlyList<Guid>> GetInvalidAssetIdsAsync(IEnumerable<Guid> assetId)
{
return checkAsset(assetId);
}
}
}

44
src/Squidex.Domain.Apps.Core/Schemas/Validators/AllowedValuesValidator.cs

@ -1,44 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class AllowedValuesValidator<T> : IValidator
{
private readonly T[] allowedValues;
public AllowedValuesValidator(params T[] allowedValues)
{
Guard.NotNull(allowedValues, nameof(allowedValues));
this.allowedValues = allowedValues;
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value == null)
{
return TaskHelper.Done;
}
var typedValue = (T)value;
if (!allowedValues.Contains(typedValue))
{
addError("<FIELD> is not an allowed value.");
}
return TaskHelper.Done;
}
}
}

29
src/Squidex.Domain.Apps.Core/Schemas/Validators/AssetsValidator.cs

@ -1,29 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class AssetsValidator : IValidator
{
public async Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value is ICollection<Guid> assetIds)
{
var invalidIds = await context.GetInvalidAssetIdsAsync(assetIds);
foreach (var invalidId in invalidIds)
{
addError($"<FIELD> contains invalid asset '{invalidId}'.");
}
}
}
}
}

47
src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionItemValidator.cs

@ -1,47 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class CollectionItemValidator<T> : IValidator
{
private readonly IValidator[] itemValidators;
public CollectionItemValidator(params IValidator[] itemValidators)
{
Guard.NotNull(itemValidators, nameof(itemValidators));
Guard.NotEmpty(itemValidators, nameof(itemValidators));
this.itemValidators = itemValidators;
}
public async Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value is ICollection<T> items)
{
var innerContext = context.Optional(false);
var index = 1;
foreach (var item in items)
{
foreach (var itemValidator in itemValidators)
{
await itemValidator.ValidateAsync(item, innerContext, e => addError(e.Replace("<FIELD>", $"<FIELD> item #{index}")));
}
index++;
}
}
}
}
}

53
src/Squidex.Domain.Apps.Core/Schemas/Validators/CollectionValidator.cs

@ -1,53 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class CollectionValidator<T> : IValidator
{
private readonly bool isRequired;
private readonly int? minItems;
private readonly int? maxItems;
public CollectionValidator(bool isRequired, int? minItems = null, int? maxItems = null)
{
this.isRequired = isRequired;
this.minItems = minItems;
this.maxItems = maxItems;
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (!(value is ICollection<T> items) || items.Count == 0)
{
if (isRequired && !context.IsOptional)
{
addError("<FIELD> is required.");
}
return TaskHelper.Done;
}
if (minItems.HasValue && items.Count < minItems.Value)
{
addError($"<FIELD> must have at least {minItems} item(s).");
}
if (maxItems.HasValue && items.Count > maxItems.Value)
{
addError($"<FIELD> must have not more than {maxItems} item(s).");
}
return TaskHelper.Done;
}
}
}

17
src/Squidex.Domain.Apps.Core/Schemas/Validators/IValidator.cs

@ -1,17 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public interface IValidator
{
Task ValidateAsync(object value, ValidationContext context, Action<string> addError);
}
}

47
src/Squidex.Domain.Apps.Core/Schemas/Validators/PatternValidator.cs

@ -1,47 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public class PatternValidator : IValidator
{
private readonly Regex regex;
private readonly string errorMessage;
public PatternValidator(string pattern, string errorMessage = null)
{
this.errorMessage = errorMessage;
regex = new Regex("^" + pattern + "$");
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value is string stringValue)
{
if (!string.IsNullOrEmpty(stringValue) && !regex.IsMatch(stringValue))
{
if (string.IsNullOrWhiteSpace(errorMessage))
{
addError("<FIELD> is not valid.");
}
else
{
addError(errorMessage);
}
}
}
return TaskHelper.Done;
}
}
}

52
src/Squidex.Domain.Apps.Core/Schemas/Validators/RangeValidator.cs

@ -1,52 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class RangeValidator<T> : IValidator where T : struct, IComparable<T>
{
private readonly T? min;
private readonly T? max;
public RangeValidator(T? min, T? max)
{
if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) >= 0)
{
throw new ArgumentException("Min value must be greater than max value.", nameof(min));
}
this.min = min;
this.max = max;
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value == null)
{
return TaskHelper.Done;
}
var typedValue = (T)value;
if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{
addError($"<FIELD> must be greater or equals than '{min}'.");
}
if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{
addError($"<FIELD> must be less or equals than '{max}'.");
}
return TaskHelper.Done;
}
}
}

36
src/Squidex.Domain.Apps.Core/Schemas/Validators/ReferencesValidator.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public sealed class ReferencesValidator : IValidator
{
private readonly Guid schemaId;
public ReferencesValidator(Guid schemaId)
{
this.schemaId = schemaId;
}
public async Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value is ICollection<Guid> contentIds)
{
var invalidIds = await context.GetInvalidContentIdsAsync(contentIds, schemaId);
foreach (var invalidId in invalidIds)
{
addError($"<FIELD> contains invalid reference '{invalidId}'.");
}
}
}
}
}

40
src/Squidex.Domain.Apps.Core/Schemas/Validators/RequiredStringValidator.cs

@ -1,40 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public class RequiredStringValidator : IValidator
{
private readonly bool validateEmptyStrings;
public RequiredStringValidator(bool validateEmptyStrings = false)
{
this.validateEmptyStrings = validateEmptyStrings;
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (context.IsOptional || (value != null && !(value is string)))
{
return TaskHelper.Done;
}
var valueAsString = (string)value;
if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString)))
{
addError("<FIELD> is required.");
}
return TaskHelper.Done;
}
}
}

26
src/Squidex.Domain.Apps.Core/Schemas/Validators/RequiredValidator.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public class RequiredValidator : IValidator
{
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value == null && !context.IsOptional)
{
addError("<FIELD> is required.");
}
return TaskHelper.Done;
}
}
}

48
src/Squidex.Domain.Apps.Core/Schemas/Validators/StringLengthValidator.cs

@ -1,48 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Schemas.Validators
{
public class StringLengthValidator : IValidator
{
private readonly int? minLength;
private readonly int? maxLength;
public StringLengthValidator(int? minLength, int? maxLength)
{
if (minLength.HasValue && maxLength.HasValue && minLength.Value >= maxLength.Value)
{
throw new ArgumentException("Min length must be greater than max length.", nameof(minLength));
}
this.minLength = minLength;
this.maxLength = maxLength;
}
public Task ValidateAsync(object value, ValidationContext context, Action<string> addError)
{
if (value is string stringValue && !string.IsNullOrEmpty(stringValue))
{
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;
}
}
}

131
src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentDataObject.cs

@ -1,131 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
#pragma warning disable RECS0133 // Parameter name differs in base declaration
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentDataObject : ObjectInstance
{
private readonly NamedContentData contentData;
private HashSet<string> fieldsToDelete;
private Dictionary<string, ContentDataProperty> fieldProperties;
private bool isChanged;
public ContentDataObject(Engine engine, NamedContentData contentData)
: base(engine)
{
Extensible = true;
this.contentData = contentData;
}
public void MarkChanged()
{
isChanged = true;
}
public bool TryUpdate(out NamedContentData result)
{
result = contentData;
if (isChanged)
{
if (fieldsToDelete != null)
{
foreach (var field in fieldsToDelete)
{
contentData.Remove(field);
}
}
if (fieldProperties != null)
{
foreach (var kvp in fieldProperties)
{
if (kvp.Value.ContentField.TryUpdate(out var fieldData))
{
contentData[kvp.Key] = fieldData;
}
}
}
}
return isChanged;
}
public override void RemoveOwnProperty(string propertyName)
{
if (fieldsToDelete == null)
{
fieldsToDelete = new HashSet<string>();
}
fieldsToDelete.Add(propertyName);
fieldProperties?.Remove(propertyName);
MarkChanged();
}
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
{
EnsurePropertiesInitialized();
if (!fieldProperties.ContainsKey(propertyName))
{
fieldProperties[propertyName] = new ContentDataProperty(this) { Value = desc.Value };
}
return true;
}
public override void Put(string propertyName, JsValue value, bool throwOnError)
{
EnsurePropertiesInitialized();
fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this)).Value = value;
}
public override PropertyDescriptor GetOwnProperty(string propertyName)
{
EnsurePropertiesInitialized();
return fieldProperties.GetOrDefault(propertyName) ?? new PropertyDescriptor(new ObjectInstance(Engine) { Extensible = true }, true, false, true);
}
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()
{
EnsurePropertiesInitialized();
foreach (var property in fieldProperties)
{
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value);
}
}
private void EnsurePropertiesInitialized()
{
if (fieldProperties == null)
{
fieldProperties = new Dictionary<string, ContentDataProperty>(contentData.Count);
foreach (var kvp in contentData)
{
fieldProperties.Add(kvp.Key, new ContentDataProperty(this, new ContentFieldObject(this, kvp.Value, false)));
}
}
}
}
}

67
src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentDataProperty.cs

@ -1,67 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint.Native;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentDataProperty : PropertyDescriptor
{
private readonly ContentDataObject contentData;
private ContentFieldObject contentField;
private JsValue value;
public override JsValue Value
{
get
{
return value;
}
set
{
if (!Equals(this.value, value))
{
if (value == null || !value.IsObject())
{
throw new JavaScriptException("Can only assign object to content data.");
}
var obj = value.AsObject();
contentField = new ContentFieldObject(contentData, new ContentFieldData(), true);
foreach (var kvp in obj.GetOwnProperties())
{
contentField.Put(kvp.Key, kvp.Value.Value, true);
}
this.value = new JsValue(contentField);
}
}
}
public ContentFieldObject ContentField
{
get { return contentField; }
}
public ContentDataProperty(ContentDataObject contentData, ContentFieldObject contentField = null)
: base(null, true, true, true)
{
this.contentData = contentData;
this.contentField = contentField;
if (contentField != null)
{
value = new JsValue(contentField);
}
}
}
}

136
src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentFieldObject.cs

@ -1,136 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
#pragma warning disable RECS0133 // Parameter name differs in base declaration
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentFieldObject : ObjectInstance
{
private readonly ContentDataObject contentData;
private readonly ContentFieldData fieldData;
private HashSet<string> valuesToDelete;
private Dictionary<string, ContentFieldProperty> valueProperties;
private bool isChanged;
public ContentFieldData FieldData
{
get { return fieldData; }
}
public ContentFieldObject(ContentDataObject contentData, ContentFieldData fieldData, bool isNew)
: base(contentData.Engine)
{
Extensible = true;
this.contentData = contentData;
this.fieldData = fieldData;
if (isNew)
{
MarkChanged();
}
}
public void MarkChanged()
{
isChanged = true;
contentData.MarkChanged();
}
public bool TryUpdate(out ContentFieldData result)
{
result = fieldData;
if (isChanged)
{
if (valuesToDelete != null)
{
foreach (var field in valuesToDelete)
{
fieldData.Remove(field);
}
}
if (valueProperties != null)
{
foreach (var kvp in valueProperties)
{
if (kvp.Value.IsChanged)
{
fieldData[kvp.Key] = kvp.Value.ContentValue;
}
}
}
}
return isChanged;
}
public override void RemoveOwnProperty(string propertyName)
{
if (valuesToDelete == null)
{
valuesToDelete = new HashSet<string>();
}
valuesToDelete.Add(propertyName);
valueProperties?.Remove(propertyName);
MarkChanged();
}
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
{
EnsurePropertiesInitialized();
if (!valueProperties.ContainsKey(propertyName))
{
valueProperties[propertyName] = new ContentFieldProperty(this) { Value = desc.Value };
}
return true;
}
public override PropertyDescriptor GetOwnProperty(string propertyName)
{
EnsurePropertiesInitialized();
return valueProperties?.GetOrDefault(propertyName) ?? PropertyDescriptor.Undefined;
}
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties()
{
EnsurePropertiesInitialized();
foreach (var property in valueProperties)
{
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value);
}
}
private void EnsurePropertiesInitialized()
{
if (valueProperties == null)
{
valueProperties = new Dictionary<string, ContentFieldProperty>(FieldData.Count);
foreach (var kvp in FieldData)
{
valueProperties.Add(kvp.Key, new ContentFieldProperty(this, kvp.Value));
}
}
}
}
}

58
src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -1,58 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint.Native;
using Jint.Runtime.Descriptors;
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentFieldProperty : PropertyDescriptor
{
private readonly ContentFieldObject contentField;
private JToken contentValue;
private JsValue value;
private bool isChanged;
public override JsValue Value
{
get
{
return value ?? (value = JsonMapper.Map(contentValue, contentField.Engine));
}
set
{
if (!Equals(this.value, value))
{
this.value = value;
contentValue = null;
contentField.MarkChanged();
isChanged = true;
}
}
}
public JToken ContentValue
{
get { return contentValue ?? (contentValue = JsonMapper.Map(value)); }
}
public bool IsChanged
{
get { return isChanged; }
}
public ContentFieldProperty(ContentFieldObject contentField, JToken contentValue = null)
: base(null, true, true, true)
{
this.contentField = contentField;
this.contentValue = contentValue;
}
}
}

145
src/Squidex.Domain.Apps.Core/Scripting/ContentWrapper/JsonMapper.cs

@ -1,145 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public static class JsonMapper
{
public static JsValue Map(JToken value, Engine engine)
{
if (value == null)
{
return JsValue.Null;
}
switch (value.Type)
{
case JTokenType.Date:
case JTokenType.Guid:
case JTokenType.String:
case JTokenType.Uri:
case JTokenType.TimeSpan:
return new JsValue((string)value);
case JTokenType.Null:
return JsValue.Null;
case JTokenType.Undefined:
return JsValue.Undefined;
case JTokenType.Integer:
return new JsValue((long)value);
case JTokenType.Float:
return new JsValue((double)value);
case JTokenType.Boolean:
return new JsValue((bool)value);
case JTokenType.Object:
return FromObject(value, engine);
case JTokenType.Array:
{
var arr = (JArray)value;
var target = new JsValue[arr.Count];
for (var i = 0; i < arr.Count; i++)
{
target[i] = Map(arr[i], engine);
}
return engine.Array.Construct(target);
}
}
throw new ArgumentException("Invalid json type.", nameof(value));
}
private static JsValue FromObject(JToken value, Engine engine)
{
var obj = (JObject)value;
var target = new ObjectInstance(engine);
foreach (var property in obj)
{
target.FastAddProperty(property.Key, Map(property.Value, engine), false, true, true);
}
return target;
}
public static JToken Map(JsValue value)
{
if (value == null || value.IsNull())
{
return JValue.CreateNull();
}
if (value.IsUndefined())
{
return JValue.CreateUndefined();
}
if (value.IsString())
{
return new JValue(value.AsString());
}
if (value.IsBoolean())
{
return new JValue(value.AsBoolean());
}
if (value.IsNumber())
{
return new JValue(value.AsNumber());
}
if (value.IsDate())
{
return new JValue(value.AsDate().ToDateTime());
}
if (value.IsRegExp())
{
return JValue.CreateString(value.AsRegExp().Value?.ToString());
}
if (value.IsArray())
{
var arr = value.AsArray();
var target = new JArray();
for (var i = 0; i < arr.GetLength(); i++)
{
target.Add(Map(arr.Get(i.ToString())));
}
return target;
}
if (value.IsObject())
{
var obj = value.AsObject();
var target = new JObject();
foreach (var kvp in obj.GetOwnProperties())
{
target[kvp.Key] = Map(kvp.Value.Value);
}
return target;
}
throw new ArgumentException("Invalid json type.", nameof(value));
}
}
}

20
src/Squidex.Domain.Apps.Core/Scripting/IScriptEngine.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting
{
public interface IScriptEngine
{
void Execute(ScriptContext context, string script);
NamedContentData ExecuteAndTransform(ScriptContext context, string script);
NamedContentData Transform(ScriptContext context, string script);
}
}

177
src/Squidex.Domain.Apps.Core/Scripting/JintScriptEngine.cs

@ -1,177 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Jint;
using Jint.Native.Object;
using Jint.Parser;
using Jint.Runtime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class JintScriptEngine : IScriptEngine
{
public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(200);
public void Execute(ScriptContext context, string script)
{
Guard.NotNull(context, nameof(context));
if (!string.IsNullOrWhiteSpace(script))
{
var engine = CreateScriptEngine(context);
EnableDisallow(engine);
EnableReject(engine);
Execute(engine, script);
}
}
public NamedContentData ExecuteAndTransform(ScriptContext context, string script)
{
Guard.NotNull(context, nameof(context));
var result = context.Data;
if (!string.IsNullOrWhiteSpace(script))
{
var engine = CreateScriptEngine(context);
EnableDisallow(engine);
EnableReject(engine);
engine.SetValue("operation", new Action(() =>
{
var dataInstance = engine.GetValue("ctx").AsObject().Get("data");
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data)
{
data.TryUpdate(out result);
}
}));
engine.SetValue("replace", new Action(() =>
{
var dataInstance = engine.GetValue("ctx").AsObject().Get("data");
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data)
{
data.TryUpdate(out result);
}
}));
Execute(engine, script);
}
return result;
}
public NamedContentData Transform(ScriptContext context, string script)
{
Guard.NotNull(context, nameof(context));
var result = context.Data;
if (!string.IsNullOrWhiteSpace(script))
{
try
{
var engine = CreateScriptEngine(context);
engine.SetValue("replace", new Action(() =>
{
var dataInstance = engine.GetValue("ctx").AsObject().Get("data");
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data)
{
data.TryUpdate(out result);
}
}));
engine.Execute(script);
}
catch (Exception)
{
result = context.Data;
}
}
return result;
}
private static void Execute(Engine engine, string script)
{
try
{
engine.Execute(script);
}
catch (ParserException ex)
{
throw new ValidationException($"Failed to execute script with javascript syntaxs error.", new ValidationError(ex.Message));
}
catch (JavaScriptException ex)
{
throw new ValidationException($"Failed to execute script with javascript error.", new ValidationError(ex.Message));
}
}
private Engine CreateScriptEngine(ScriptContext context)
{
var engine = new Engine(options => options.TimeoutInterval(Timeout).Strict());
var contextInstance = new ObjectInstance(engine);
if (context.Data != null)
{
contextInstance.FastAddProperty("data", new ContentDataObject(engine, context.Data), true, true, true);
}
if (context.OldData != null)
{
contextInstance.FastAddProperty("oldData", new ContentDataObject(engine, context.OldData), true, true, true);
}
if (context.User != null)
{
contextInstance.FastAddProperty("user", new JintUser(engine, context.User), false, true, false);
}
if (!string.IsNullOrWhiteSpace(context.Operation))
{
contextInstance.FastAddProperty("operation", context.Operation, false, true, false);
}
engine.SetValue("ctx", contextInstance);
return engine;
}
private static void EnableDisallow(Engine engine)
{
engine.SetValue("disallow", new Action<string>(message =>
{
var exMessage = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed";
throw new DomainForbiddenException(exMessage);
}));
}
private static void EnableReject(Engine engine)
{
engine.SetValue("reject", new Action<string>(message =>
{
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null;
throw new ValidationException($"Script rejected the operation.", errors);
}));
}
}
}

49
src/Squidex.Domain.Apps.Core/Scripting/JintUser.cs

@ -1,49 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using System.Security.Claims;
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class JintUser : ObjectInstance
{
public JintUser(Engine engine, ClaimsPrincipal principal)
: base(engine)
{
var subjectId = principal.OpenIdSubject();
var isClient = string.IsNullOrWhiteSpace(subjectId);
if (!isClient)
{
FastAddProperty("id", subjectId, false, true, false);
FastAddProperty("isClient", false, false, true, false);
}
else
{
FastAddProperty("id", principal.OpenIdClientId(), false, true, false);
FastAddProperty("isClient", true, false, true, false);
}
FastAddProperty("email", principal.OpenIdEmail(), false, true, false);
var claimsInstance = new ObjectInstance(engine);
foreach (var group in principal.Claims.GroupBy(x => x.Type))
{
claimsInstance.FastAddProperty(group.Key, engine.Array.Construct(group.Select(x => new JsValue(x.Value)).ToArray()), false, true, false);
}
FastAddProperty("claims", claimsInstance, false, true, false);
}
}
}

26
src/Squidex.Domain.Apps.Core/Scripting/ScriptContext.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Security.Claims;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting
{
public sealed class ScriptContext
{
public ClaimsPrincipal User { get; set; }
public Guid ContentId { get; set; }
public NamedContentData Data { get; set; }
public NamedContentData OldData { get; set; }
public string Operation { get; set; }
}
}

29
src/Squidex.Domain.Apps.Core/Squidex.Domain.Apps.Core.csproj

@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.23" />
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NJsonSchema" Version="9.8.3" />
<PackageReference Include="NodaTime" Version="2.2.1" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

24
src/Squidex.Domain.Apps.Core/Webhooks/WebhookSchema.cs

@ -1,24 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Core.Webhooks
{
public sealed class WebhookSchema
{
public Guid SchemaId { get; set; }
public bool SendCreate { get; set; }
public bool SendUpdate { get; set; }
public bool SendDelete { get; set; }
public bool SendPublish { get; set; }
}
}

4
src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -16,10 +16,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.4.4" /> <PackageReference Include="Microsoft.OData.Core" Version="7.4.4" />
<PackageReference Include="MongoDB.Driver" Version="2.5.1" /> <PackageReference Include="MongoDB.Driver" Version="2.6.1" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

8
src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -15,12 +15,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GraphQL" Version="2.0.0-alpha-912" /> <PackageReference Include="GraphQL" Version="2.0.0-alpha-912" />
<PackageReference Include="Microsoft.Orleans.OrleansCodeGenerator.Build" Version="2.0.0-ci.20180515.5" /> <PackageReference Include="Microsoft.Orleans.OrleansCodeGenerator.Build" Version="2.0.4-ci.20180622.3" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.0.0-ci.20180515.5" /> <PackageReference Include="Microsoft.Orleans.Core" Version="2.0.4-ci.20180622.3" />
<PackageReference Include="NodaTime" Version="2.2.5" /> <PackageReference Include="NodaTime" Version="2.3.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

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

@ -11,12 +11,12 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.1.1" />
<PackageReference Include="NodaTime" Version="2.2.5" /> <PackageReference Include="NodaTime" Version="2.3.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.8.0" /> <PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.9.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

8
src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -14,12 +14,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IdentityServer4" Version="2.2.0" /> <PackageReference Include="IdentityServer4" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
<PackageReference Include="MongoDB.Driver" Version="2.5.1" /> <PackageReference Include="MongoDB.Driver" Version="2.6.1" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.4.1" /> <PackageReference Include="System.Security.Principal.Windows" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

8
src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -11,13 +11,13 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.1" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="SharpPwned.NET" Version="1.0.2-fix" /> <PackageReference Include="SharpPwned.NET" Version="1.0.8" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Linq.Queryable" Version="4.3.0" /> <PackageReference Include="System.Linq.Queryable" Version="4.3.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.4.1" /> <PackageReference Include="System.Security.Principal.Windows" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

2
src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj

@ -6,7 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="WindowsAzure.Storage" Version="9.1.1" /> <PackageReference Include="WindowsAzure.Storage" Version="9.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />

2
src/Squidex.Infrastructure.GoogleCloud/Squidex.Infrastructure.GoogleCloud.csproj

@ -11,7 +11,7 @@
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.1.0" /> <PackageReference Include="Google.Cloud.Storage.V1" Version="2.1.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />

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

Loading…
Cancel
Save