mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
180 changed files with 419 additions and 5791 deletions
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; } |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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; } |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 }; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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}'."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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++; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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}'."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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))); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; } |
|
||||
} |
|
||||
} |
|
||||
@ -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> |
|
||||
@ -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; } |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue