Browse Source

ContentData refactored and bugfixes with asset editor.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
dbcb576f1d
  1. 11
      src/Squidex.Core/ContentEnricher.cs
  2. 67
      src/Squidex.Core/ContentExtensions.cs
  3. 6
      src/Squidex.Core/ContentValidator.cs
  4. 233
      src/Squidex.Core/Contents/ContentData.cs
  5. 133
      src/Squidex.Core/Contents/IdContentData.cs
  6. 193
      src/Squidex.Core/Contents/NamedContentData.cs
  7. 2
      src/Squidex.Core/Schemas/AssetsField.cs
  8. 2
      src/Squidex.Events/Contents/ContentCreated.cs
  9. 2
      src/Squidex.Events/Contents/ContentUpdated.cs
  10. 22
      src/Squidex.Infrastructure.MongoDb/IMongoEntity.cs
  11. 2
      src/Squidex.Infrastructure.MongoDb/MongoEntity.cs
  12. 78
      src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
  13. 3
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  14. 18
      src/Squidex.Read.MongoDb/Utils/EntityMapper.cs
  15. 6
      src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs
  16. 2
      src/Squidex.Read/Contents/IContentEntity.cs
  17. 2
      src/Squidex.Write/Contents/Commands/ContentDataCommand.cs
  18. 2
      src/Squidex.Write/Contents/ContentDomainObject.cs
  19. 8
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  20. 2
      src/Squidex/Controllers/ContentApi/Models/ContentDto.cs
  21. 16
      src/Squidex/app/shared/components/assets-editor.component.ts
  22. 2
      tests/Squidex.Core.Tests/ContentEnrichmentTests.cs
  23. 30
      tests/Squidex.Core.Tests/ContentValidationTests.cs
  24. 255
      tests/Squidex.Core.Tests/Contents/ContentDataTests.cs
  25. 81
      tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs
  26. 12
      tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs
  27. 8
      tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

11
src/Squidex.Core/ContentEnricher.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure.Json;
namespace Squidex.Core
{
public sealed class ContentEnricher
public sealed class ContentEnricher<T>
{
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
@ -29,13 +29,14 @@ namespace Squidex.Core
this.partitionResolver = partitionResolver;
}
public void Enrich(ContentData data)
public void Enrich(ContentData<T> data)
{
Guard.NotNull(data, nameof(data));
foreach (var field in schema.FieldsByName.Values)
foreach (var field in schema.Fields)
{
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
var fieldKey = data.GetKey(field);
var fieldData = data.GetOrCreate(fieldKey, k => new ContentFieldData());
var fieldPartition = partitionResolver(field.Paritioning);
foreach (var partitionItem in fieldPartition)
@ -45,7 +46,7 @@ namespace Squidex.Core
if (fieldData.Count > 0)
{
data.AddField(field.Name, fieldData);
data[fieldKey] = fieldData;
}
}
}

67
src/Squidex.Core/ContentExtensions.cs

@ -6,9 +6,7 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
@ -20,16 +18,14 @@ namespace Squidex.Core
{
public static class ContentExtensions
{
public static ContentData Enrich(this ContentData data, Schema schema, PartitionResolver partitionResolver)
public static void Enrich<T>(this ContentData<T> data, Schema schema, PartitionResolver partitionResolver)
{
var enricher = new ContentEnricher(schema, partitionResolver);
var enricher = new ContentEnricher<T>(schema, partitionResolver);
enricher.Enrich(data);
return data;
}
public static async Task ValidateAsync(this ContentData data, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors)
public static async Task ValidateAsync(this NamedContentData data, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, partitionResolver);
@ -41,7 +37,7 @@ namespace Squidex.Core
}
}
public static async Task ValidatePartialAsync(this ContentData data, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors)
public static async Task ValidatePartialAsync(this NamedContentData data, Schema schema, PartitionResolver partitionResolver, IList<ValidationError> errors)
{
var validator = new ContentValidator(schema, partitionResolver);
@ -52,60 +48,5 @@ namespace Squidex.Core
errors.Add(error);
}
}
public static IEnumerable<Guid> GetReferencedIds(this ContentData data, Schema schema)
{
var foundReferences = new HashSet<Guid>();
foreach (var field in schema.Fields)
{
if (field is IReferenceField referenceField)
{
var fieldData = data.GetOrDefault(field.Id.ToString());
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 static ContentData ToCleanedReferences(this ContentData data, Schema schema, ISet<Guid> deletedReferencedIds)
{
var result = new ContentData(data);
foreach (var field in schema.Fields)
{
if (field is IReferenceField referenceField)
{
var fieldData = data.GetOrDefault(field.Id.ToString());
if (fieldData == null)
{
continue;
}
foreach (var partitionValue in fieldData.Where(x => x.Value != null).ToList())
{
var newValue = referenceField.RemoveDeletedReferences(partitionValue.Value, deletedReferencedIds);
fieldData[partitionValue.Key] = newValue;
}
}
}
return result;
}
}
}

6
src/Squidex.Core/ContentValidator.cs

@ -38,7 +38,7 @@ namespace Squidex.Core
this.partitionResolver = partitionResolver;
}
public async Task ValidatePartialAsync(ContentData data)
public async Task ValidatePartialAsync(NamedContentData data)
{
Guard.NotNull(data, nameof(data));
@ -77,7 +77,7 @@ namespace Squidex.Core
}
}
public async Task ValidateAsync(ContentData data)
public async Task ValidateAsync(NamedContentData data)
{
Guard.NotNull(data, nameof(data));
@ -91,7 +91,7 @@ namespace Squidex.Core
}
}
private void ValidateUnknownFields(ContentData data)
private void ValidateUnknownFields(NamedContentData data)
{
foreach (var fieldData in data)
{

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

@ -9,7 +9,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
@ -18,41 +17,33 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Contents
{
public sealed class ContentData : Dictionary<string, ContentFieldData>, IEquatable<ContentData>
public abstract class ContentData<T> : Dictionary<T, ContentFieldData>, IEquatable<ContentData<T>>
{
public ContentData()
: base(StringComparer.OrdinalIgnoreCase)
public IEnumerable<KeyValuePair<T, ContentFieldData>> ValidValues
{
get { return this.Where(x => x.Value != null); }
}
public ContentData(IDictionary<string, ContentFieldData> copy)
: base(copy, StringComparer.OrdinalIgnoreCase)
protected ContentData(IEqualityComparer<T> comparer)
: base(comparer)
{
}
public ContentData AddField(string fieldName, ContentFieldData data)
protected ContentData(IDictionary<T, ContentFieldData> copy, IEqualityComparer<T> comparer)
: base(copy, comparer)
{
Guard.ValidPropertyName(fieldName, nameof(fieldName));
this[fieldName] = data;
return this;
}
public ContentData MergeInto(ContentData other)
protected static TResult Merge<TResult>(TResult source, TResult target) where TResult : ContentData<T>
{
Guard.NotNull(other, nameof(other));
var result = new ContentData(this);
if (ReferenceEquals(other, this))
if (ReferenceEquals(target, source))
{
return result;
return source;
}
foreach (var otherValue in other)
foreach (var otherValue in source)
{
var fieldValue = result.GetOrAdd(otherValue.Key, x => new ContentFieldData());
var fieldValue = target.GetOrAdd(otherValue.Key, x => new ContentFieldData());
foreach (var value in otherValue.Value)
{
@ -60,223 +51,85 @@ namespace Squidex.Core.Contents
}
}
return result;
return target;
}
public ContentData ToCleaned()
protected static TResult Clean<TResult>(TResult source, TResult target) where TResult : ContentData<T>
{
var result = new ContentData();
foreach (var fieldValue in this.Where(x => x.Value != null))
foreach (var fieldValue in source.ValidValues)
{
var resultValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value.Where(x => x.Value != null && x.Value.Type != JTokenType.Null))
foreach (var partitionValue in fieldValue.Value.Where(x => IsNotNull(x.Value)))
{
resultValue[partitionValue.Key] = partitionValue.Value;
}
if (resultValue.Count > 0)
{
result[fieldValue.Key] = resultValue;
target[fieldValue.Key] = resultValue;
}
}
return result;
return target;
}
public ContentData ToIdModel(Schema schema, bool encodeJsonField)
public IEnumerable<Guid> GetReferencedIds(Schema schema)
{
Guard.NotNull(schema, nameof(schema));
var result = new ContentData();
var foundReferences = new HashSet<Guid>();
foreach (var fieldValue in this)
foreach (var field in schema.Fields)
{
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out Field field))
{
continue;
}
var fieldId = field.Id.ToString();
if (encodeJsonField && field is JsonField)
if (field is IReferenceField referenceField)
{
var encodedValue = new ContentFieldData();
var fieldKey = GetKey(field);
var fieldData = this.GetOrDefault(fieldKey);
foreach (var partitionValue in fieldValue.Value)
if (fieldData == null)
{
if (partitionValue.Value == null || partitionValue.Value.Type == JTokenType.Null)
{
encodedValue[partitionValue.Key] = null;
}
else
{
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString()));
encodedValue[partitionValue.Key] = value;
}
continue;
}
result[fieldId] = encodedValue;
}
else
{
result[fieldId] = fieldValue.Value;
}
}
return result;
}
public ContentData ToNameModel(Schema schema, bool decodeJsonField)
{
Guard.NotNull(schema, nameof(schema));
var result = new ContentData();
foreach (var fieldValue in this)
{
if (!long.TryParse(fieldValue.Key, out long fieldId) || !schema.FieldsById.TryGetValue(fieldId, out Field field))
{
continue;
}
if (decodeJsonField && field is JsonField)
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value)
foreach (var partitionValue in fieldData.Where(x => x.Value != null))
{
if (partitionValue.Value == null || partitionValue.Value.Type == JTokenType.Null)
{
encodedValue[partitionValue.Key] = null;
}
else
{
var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString()));
var ids = referenceField.GetReferencedIds(partitionValue.Value);
encodedValue[partitionValue.Key] = JToken.Parse(value);
foreach (var id in ids.Where(x => foundReferences.Add(x)))
{
yield return id;
}
}
result[field.Name] = encodedValue;
}
else
{
result[field.Name] = fieldValue.Value;
}
}
return result;
}
public ContentData ToApiModel(Schema schema, LanguagesConfig languagesConfig, IReadOnlyCollection<Language> languagePreferences = null, bool excludeHidden = true)
public override bool Equals(object obj)
{
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 ContentData();
foreach (var fieldValue in this)
{
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out Field field) || (excludeHidden && field.IsHidden))
{
continue;
}
var fieldResult = new ContentFieldData();
var fieldValues = fieldValue.Value;
if (field.Paritioning.Equals(Partitioning.Language))
{
foreach (var languageConfig in languagesConfig)
{
var languageCode = languageConfig.Key;
if (fieldValues.TryGetValue(languageCode, out JToken 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 JToken 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(field.Name, fieldResult);
}
return result;
return Equals(obj as ContentData<T>);
}
public object ToLanguageModel(LanguagesConfig languagesConfig, IReadOnlyCollection<Language> languagePreferences = null)
public bool Equals(ContentData<T> other)
{
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.Fallback).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 JToken value) && value != null)
{
result[fieldValue.Key] = value;
break;
}
}
}
return result;
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
}
public override bool Equals(object obj)
public override int GetHashCode()
{
return Equals(obj as ContentData);
return this.DictionaryHashCode();
}
public bool Equals(ContentData other)
protected static bool IsNull(JToken value)
{
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
return value == null || value.Type == JTokenType.Null;
}
public override int GetHashCode()
protected static bool IsNotNull(JToken value)
{
return this.DictionaryHashCode();
return value != null && value.Type != JTokenType.Null;
}
public abstract T GetKey(Field field);
}
}

133
src/Squidex.Core/Contents/IdContentData.cs

@ -0,0 +1,133 @@
// ==========================================================================
// NamedContentData.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.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 => IsNotNull(x.Value)).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 Field field))
{
continue;
}
if (decodeJsonField && field is JsonField)
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value)
{
if (IsNull(partitionValue.Value))
{
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;
}
}
}

193
src/Squidex.Core/Contents/NamedContentData.cs

@ -0,0 +1,193 @@
// ==========================================================================
// NamedContentData.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.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 Field field))
{
continue;
}
var fieldId = field.Id;
if (encodeJsonField && field is JsonField)
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value)
{
if (IsNull(partitionValue.Value))
{
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, IReadOnlyCollection<Language> languagePreferences = null, 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 Field field) || (excludeHidden && field.IsHidden))
{
continue;
}
var fieldResult = new ContentFieldData();
var fieldValues = fieldValue.Value;
if (field.Paritioning.Equals(Partitioning.Language))
{
foreach (var languageConfig in languagesConfig)
{
var languageCode = languageConfig.Key;
if (fieldValues.TryGetValue(languageCode, out JToken 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 JToken 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.Fallback).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 JToken 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;
}
}
}

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

@ -54,7 +54,7 @@ namespace Squidex.Core.Schemas
public JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds)
{
if (value == null)
if (value == null || value.Type == JTokenType.Null)
{
return null;
}

2
src/Squidex.Events/Contents/ContentCreated.cs

@ -14,6 +14,6 @@ namespace Squidex.Events.Contents
[TypeName("ContentCreatedEvent")]
public class ContentCreated : ContentEvent
{
public ContentData Data { get; set; }
public NamedContentData Data { get; set; }
}
}

2
src/Squidex.Events/Contents/ContentUpdated.cs

@ -14,6 +14,6 @@ namespace Squidex.Events.Contents
[TypeName("ContentUpdatedEvent")]
public class ContentUpdated : ContentEvent
{
public ContentData Data { get; set; }
public NamedContentData Data { get; set; }
}
}

22
src/Squidex.Infrastructure.MongoDb/IMongoEntity.cs

@ -0,0 +1,22 @@
// ==========================================================================
// IMongoEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using NodaTime;
namespace Squidex.Infrastructure.MongoDb
{
public interface IMongoEntity
{
Guid Id { get; set; }
Instant Created { get; set; }
Instant LastModified { get; set; }
}
}

2
src/Squidex.Infrastructure.MongoDb/MongoEntity.cs

@ -13,7 +13,7 @@ using NodaTime;
namespace Squidex.Infrastructure.MongoDb
{
public abstract class MongoEntity
public abstract class MongoEntity : IMongoEntity
{
[BsonId]
[BsonElement]

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

@ -14,7 +14,7 @@ using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
using Squidex.Core;
using NodaTime;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
@ -27,53 +27,66 @@ using JsonConvert = Newtonsoft.Json.JsonConvert;
namespace Squidex.Read.MongoDb.Contents
{
public sealed class MongoContentEntity : MongoEntity, IContentEntity
public sealed class MongoContentEntity : IContentEntity, IMongoEntity
{
private static readonly JsonWriterSettings Settings = new JsonWriterSettings { OutputMode = JsonOutputMode.Strict };
private const int MaxLength = 1024 * 1024;
private ContentData contentData;
private NamedContentData contentData;
[BsonRequired]
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonRequired]
[BsonElement("ct")]
public Instant Created { get; set; }
[BsonRequired]
[BsonElement("mt")]
public Instant LastModified { get; set; }
[BsonRequired]
[BsonElement("pu")]
public bool IsPublished { get; set; }
[BsonRequired]
[BsonElement]
public string Text { get; set; }
[BsonElement("dt")]
public string DataText { get; set; }
[BsonRequired]
[BsonElement]
[BsonElement("vs")]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
[BsonElement("ai")]
public Guid AppId { get; set; }
[BsonRequired]
[BsonElement]
[BsonElement("si")]
public Guid SchemaId { get; set; }
[BsonRequired]
[BsonElement]
[BsonElement("cb")]
public RefToken CreatedBy { get; set; }
[BsonRequired]
[BsonElement]
[BsonElement("mb")]
public RefToken LastModifiedBy { get; set; }
[BsonRequired]
[BsonElement]
public BsonDocument Data { get; set; }
[BsonElement("do")]
public BsonDocument DataObject { get; set; }
[BsonRequired]
[BsonElement]
[BsonElement("rf")]
public List<Guid> ReferencedIds { get; set; } = new List<Guid>();
[BsonRequired]
[BsonElement]
[BsonElement("rd")]
public List<Guid> ReferencedIdsDeleted { get; set; } = new List<Guid>();
ContentData IContentEntity.Data
NamedContentData IContentEntity.Data
{
get
{
@ -83,13 +96,14 @@ namespace Squidex.Read.MongoDb.Contents
public void ParseData(Schema schema)
{
if (Data != null)
if (DataObject != null)
{
var jsonString = Data.ToJson(Settings);
var jsonString = DataObject.ToJson(Settings);
contentData = JsonConvert.DeserializeObject<ContentData>(jsonString);
contentData = contentData.ToCleanedReferences(schema, new HashSet<Guid>(ReferencedIdsDeleted));
contentData = contentData.ToNameModel(schema, true);
contentData =
JsonConvert.DeserializeObject<IdContentData>(jsonString)
.ToCleanedReferences(schema, new HashSet<Guid>(ReferencedIdsDeleted))
.ToNameModel(schema, true);
}
else
{
@ -97,27 +111,27 @@ namespace Squidex.Read.MongoDb.Contents
}
}
public void SetData(Schema schema, ContentData newContentData)
public void SetData(Schema schema, NamedContentData newContentData)
{
newContentData = newContentData?.ToIdModel(schema, true);
if (newContentData != null)
{
var jsonString = JsonConvert.SerializeObject(newContentData);
var idModel = newContentData.ToIdModel(schema, true);
var jsonString = JsonConvert.SerializeObject(idModel);
DataObject = BsonDocument.Parse(jsonString);
DataText = ExtractText(idModel);
Data = BsonDocument.Parse(jsonString);
ReferencedIds = idModel.GetReferencedIds(schema).ToList();
}
else
{
Data = null;
DataObject = null;
DataText = string.Empty;
}
ReferencedIds = newContentData?.GetReferencedIds(schema).ToList() ?? new List<Guid>();
Text = ExtractText(newContentData);
}
private static string ExtractText(ContentData data)
private static string ExtractText(IdContentData data)
{
if (data == null)
{

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

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Events.Assets;
using Squidex.Events.Contents;
@ -65,7 +64,7 @@ namespace Squidex.Read.MongoDb.Contents
{
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished));
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.Text));
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.DataText));
});
}

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

@ -17,7 +17,7 @@ namespace Squidex.Read.MongoDb.Utils
{
public static class EntityMapper
{
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers) where T : MongoEntity, new()
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers) where T : IMongoEntity, new()
{
var entity = new T();
@ -32,7 +32,7 @@ namespace Squidex.Read.MongoDb.Utils
return Update(@event, headers, entity);
}
public static T Update<T>(SquidexEvent @event, EnvelopeHeaders headers, T entity) where T : MongoEntity, new()
public static T Update<T>(SquidexEvent @event, EnvelopeHeaders headers, T entity) where T : IMongoEntity, new()
{
SetVersion(headers, entity);
SetLastModified(headers, entity);
@ -41,22 +41,22 @@ namespace Squidex.Read.MongoDb.Utils
return entity;
}
private static void SetId(EnvelopeHeaders headers, MongoEntity entity)
private static void SetId(EnvelopeHeaders headers, IMongoEntity entity)
{
entity.Id = headers.AggregateId();
}
private static void SetCreated(EnvelopeHeaders headers, MongoEntity entity)
private static void SetCreated(EnvelopeHeaders headers, IMongoEntity entity)
{
entity.Created = headers.Timestamp();
}
private static void SetLastModified(EnvelopeHeaders headers, MongoEntity entity)
private static void SetLastModified(EnvelopeHeaders headers, IMongoEntity entity)
{
entity.LastModified = headers.Timestamp();
}
private static void SetVersion(EnvelopeHeaders headers, MongoEntity entity)
private static void SetVersion(EnvelopeHeaders headers, IMongoEntity entity)
{
if (entity is IEntityWithVersion withVersion)
{
@ -64,7 +64,7 @@ namespace Squidex.Read.MongoDb.Utils
}
}
private static void SetCreatedBy(SquidexEvent @event, MongoEntity entity)
private static void SetCreatedBy(SquidexEvent @event, IMongoEntity entity)
{
if (entity is IEntityWithCreatedBy withCreatedBy)
{
@ -72,7 +72,7 @@ namespace Squidex.Read.MongoDb.Utils
}
}
private static void SetLastModifiedBy(SquidexEvent @event, MongoEntity entity)
private static void SetLastModifiedBy(SquidexEvent @event, IMongoEntity entity)
{
if (entity is IEntityWithLastModifiedBy withModifiedBy)
{
@ -80,7 +80,7 @@ namespace Squidex.Read.MongoDb.Utils
}
}
private static void SetAppId(SquidexEvent @event, MongoEntity entity)
private static void SetAppId(SquidexEvent @event, IMongoEntity entity)
{
if (entity is IAppRefEntity appEntity && @event is AppEvent appEvent)
{

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

@ -18,7 +18,7 @@ namespace Squidex.Read.MongoDb.Utils
{
public static class MongoCollectionExtensions
{
public static Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : MongoEntity, new()
public static Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : IMongoEntity, new()
{
var entity = EntityMapper.Create<T>(@event, headers);
@ -27,7 +27,7 @@ namespace Squidex.Read.MongoDb.Utils
return collection.InsertOneIfNotExistsAsync(entity);
}
public static async Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Func<T, Task> updater) where T : MongoEntity, new()
public static async Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Func<T, Task> updater) where T : IMongoEntity, new()
{
var entity = EntityMapper.Create<T>(@event, headers);
@ -36,7 +36,7 @@ namespace Squidex.Read.MongoDb.Utils
await collection.InsertOneIfNotExistsAsync(entity);
}
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : MongoEntity, new()
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : IMongoEntity, new()
{
var entity = await collection.Find(t => t.Id == headers.AggregateId()).FirstOrDefaultAsync();

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

@ -14,6 +14,6 @@ namespace Squidex.Read.Contents
{
bool IsPublished { get; }
ContentData Data { get; }
NamedContentData Data { get; }
}
}

2
src/Squidex.Write/Contents/Commands/ContentDataCommand.cs

@ -14,7 +14,7 @@ namespace Squidex.Write.Contents.Commands
{
public abstract class ContentDataCommand : ContentCommand, IValidatable
{
public ContentData Data { get; set; }
public NamedContentData Data { get; set; }
public void Validate(IList<ValidationError> errors)
{

2
src/Squidex.Write/Contents/ContentDomainObject.cs

@ -23,7 +23,7 @@ namespace Squidex.Write.Contents
private bool isDeleted;
private bool isCreated;
private bool isPublished;
private ContentData data;
private NamedContentData data;
public bool IsDeleted
{

8
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -112,13 +112,13 @@ namespace Squidex.Controllers.ContentApi
[HttpPost]
[Route("content/{app}/{name}/")]
[ApiCosts(1)]
public async Task<IActionResult> PostContent([FromBody] ContentData request, [FromQuery] bool publish = false)
public async Task<IActionResult> PostContent([FromBody] NamedContentData request, [FromQuery] bool publish = false)
{
var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish };
var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<ContentData>>();
var result = context.Result<EntityCreatedResult<NamedContentData>>();
var response = ContentDto.Create(command, result);
return CreatedAtAction(nameof(GetContent), new { id = response.Id }, response);
@ -127,7 +127,7 @@ namespace Squidex.Controllers.ContentApi
[HttpPut]
[Route("content/{app}/{name}/{id}")]
[ApiCosts(1)]
public async Task<IActionResult> PutContent(Guid id, [FromBody] ContentData request)
public async Task<IActionResult> PutContent(Guid id, [FromBody] NamedContentData request)
{
var command = new UpdateContent { ContentId = id, Data = request.ToCleaned() };
@ -139,7 +139,7 @@ namespace Squidex.Controllers.ContentApi
[HttpPatch]
[Route("content/{app}/{name}/{id}")]
[ApiCosts(1)]
public async Task<IActionResult> PatchContent(Guid id, [FromBody] ContentData request)
public async Task<IActionResult> PatchContent(Guid id, [FromBody] NamedContentData request)
{
var command = new PatchContent { ContentId = id, Data = request.ToCleaned() };

2
src/Squidex/Controllers/ContentApi/Models/ContentDto.cs

@ -61,7 +61,7 @@ namespace Squidex.Controllers.ContentApi.Models
/// </summary>
public long Version { get; set; }
public static ContentDto Create(CreateContent command, EntityCreatedResult<ContentData> result)
public static ContentDto Create(CreateContent command, EntityCreatedResult<NamedContentData> result)
{
var now = SystemClock.Instance.GetCurrentInstant();

16
src/Squidex/app/shared/components/assets-editor.component.ts

@ -74,10 +74,12 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
this.oldAssets = ImmutableArray.empty<AssetDto>();
if (value && value.length > 0) {
const assetIds: string[] = value;
this.appNameOnce()
.switchMap(app => this.assetsService.getAssets(app, 10000, 0, undefined, undefined, value))
.subscribe(dtos => {
this.oldAssets = ImmutableArray.of(dtos.items);
this.oldAssets = ImmutableArray.of(assetIds.map(id => dtos.items.find(x => x.id === id)));
});
}
}
@ -100,14 +102,16 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
}
}
public onAssetLoaded(file: File, asset: AssetDto) {
this.newAssets = this.newAssets.remove(file);
this.oldAssets = this.oldAssets.pushFront(asset);
public onAssetDropped(asset: AssetDto) {
if (asset) {
this.oldAssets = this.oldAssets.pushFront(asset);
this.updateValue();
this.updateValue();
}
}
public onAssetDropped(asset: AssetDto) {
public onAssetLoaded(file: File, asset: AssetDto) {
this.newAssets = this.newAssets.remove(file);
this.oldAssets = this.oldAssets.pushFront(asset);
this.updateValue();

2
tests/Squidex.Core.Tests/ContentEnrichmentTests.cs

@ -43,7 +43,7 @@ namespace Squidex.Core
new GeolocationFieldProperties()));
var data =
new ContentData()
new NamedContentData()
.AddField("my-string",
new ContentFieldData()
.AddValue("de", "de-string"))

30
tests/Squidex.Core.Tests/ContentValidationTests.cs

@ -26,7 +26,7 @@ namespace Squidex.Core
public async Task Should_add_error_if_validating_data_with_unknown_field()
{
var data =
new ContentData()
new NamedContentData()
.AddField("unknown",
new ContentFieldData());
@ -45,7 +45,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Invariant, new NumberFieldProperties { MaxValue = 100 }));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.SetValue(1000));
@ -65,7 +65,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Invariant));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", 1)
@ -87,7 +87,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Language, new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
new NamedContentData();
await data.ValidateAsync(schema, languagesConfig.ToResolver(), errors);
@ -105,7 +105,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Invariant, new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
new NamedContentData();
await data.ValidateAsync(schema, languagesConfig.ToResolver(), errors);
@ -122,7 +122,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Language));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("de", 1)
@ -146,7 +146,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new StringField(1, "my-field", Partitioning.Language, new StringFieldProperties { IsRequired = true }));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", "value"));
@ -162,7 +162,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Language));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", 1)
@ -182,7 +182,7 @@ namespace Squidex.Core
public async Task Should_add_error_if_validating_partial_data_with_unknown_field()
{
var data =
new ContentData()
new NamedContentData()
.AddField("unknown",
new ContentFieldData());
@ -201,7 +201,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Invariant, new NumberFieldProperties { MaxValue = 100 }));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.SetValue(1000));
@ -221,7 +221,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Invariant));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", 1)
@ -243,7 +243,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Language, new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
new NamedContentData();
await data.ValidatePartialAsync(schema, languagesConfig.ToResolver(), errors);
@ -256,7 +256,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Invariant, new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
new NamedContentData();
await data.ValidatePartialAsync(schema, languagesConfig.ToResolver(), errors);
@ -269,7 +269,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Language));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("de", 1)
@ -290,7 +290,7 @@ namespace Squidex.Core
schema = schema.AddOrUpdateField(new NumberField(1, "my-field", Partitioning.Language));
var data =
new ContentData()
new NamedContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", 1)

255
tests/Squidex.Core.Tests/Contents/ContentDataTests.cs

@ -6,7 +6,10 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
@ -21,6 +24,8 @@ namespace Squidex.Core.Contents
.AddOrUpdateField(new NumberField(1, "field1", Partitioning.Language))
.AddOrUpdateField(new NumberField(2, "field2", Partitioning.Invariant))
.AddOrUpdateField(new NumberField(3, "field3", Partitioning.Invariant).Hide())
.AddOrUpdateField(new AssetsField(5, "assets1", Partitioning.Invariant, new Mock<IAssetTester>().Object))
.AddOrUpdateField(new AssetsField(6, "assets2", Partitioning.Invariant, new Mock<IAssetTester>().Object))
.AddOrUpdateField(new JsonField(4, "json", Partitioning.Language));
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.EN, Language.DE);
@ -28,7 +33,7 @@ namespace Squidex.Core.Contents
public void Should_convert_to_id_model()
{
var input =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
@ -43,12 +48,12 @@ namespace Squidex.Core.Contents
var actual = input.ToIdModel(schema, false);
var expected =
new ContentData()
.AddField("1",
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"))
.AddField("2",
.AddField(2,
new ContentFieldData()
.AddValue("iv", 3));
@ -59,7 +64,7 @@ namespace Squidex.Core.Contents
public void Should_convert_to_encoded_id_model()
{
var input =
new ContentData()
new NamedContentData()
.AddField("json",
new ContentFieldData()
.AddValue("en", new JObject())
@ -69,8 +74,8 @@ namespace Squidex.Core.Contents
var actual = input.ToIdModel(schema, true);
var expected =
new ContentData()
.AddField("4",
new IdContentData()
.AddField(4,
new ContentFieldData()
.AddValue("en", "e30=")
.AddValue("de", null)
@ -80,25 +85,25 @@ namespace Squidex.Core.Contents
}
[Fact]
public void Should_convert_from_id_model()
public void Should_convert_to_name_model()
{
var input =
new ContentData()
.AddField("1",
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"))
.AddField("2",
.AddField(2,
new ContentFieldData()
.AddValue("iv", 3))
.AddField("99",
.AddField(99,
new ContentFieldData()
.AddValue("iv", 3));
var actual = input.ToNameModel(schema, false);
var expected =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
@ -111,11 +116,11 @@ namespace Squidex.Core.Contents
}
[Fact]
public void Should_convert_from_encoded_id_model()
public void Should_convert_to_encoded_name_model()
{
var input =
new ContentData()
.AddField("4",
new IdContentData()
.AddField(4,
new ContentFieldData()
.AddValue("en", "e30=")
.AddValue("de", null)
@ -129,15 +134,8 @@ namespace Squidex.Core.Contents
[Fact]
public void Should_cleanup_old_fields()
{
var expected =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"));
var input =
new ContentData()
new NamedContentData()
.AddField("field0",
new ContentFieldData()
.AddValue("en", "en_string"))
@ -148,21 +146,21 @@ namespace Squidex.Core.Contents
var actual = input.ToApiModel(schema, languagesConfig);
Assert.Equal(expected, actual);
}
[Fact]
public void Should_cleanup_old_languages()
{
var expected =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_cleanup_old_languages()
{
var input =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
@ -171,20 +169,21 @@ namespace Squidex.Core.Contents
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_provide_invariant_from_master_language()
{
var expected =
new ContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 3));
var input =
new ContentData()
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("de", 2)
@ -192,62 +191,84 @@ namespace Squidex.Core.Contents
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 3));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_provide_master_language_from_invariant()
{
var expected =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", 3));
var input =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 3));
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", 3));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_remove_null_values_when_cleaning()
public void Should_remove_null_values_from_name_model_when_cleaning()
{
var input =
new NamedContentData()
.AddField("field1", null)
.AddField("field2",
new ContentFieldData()
.AddValue("en", 2)
.AddValue("it", null));
var actual = input.ToCleaned();
var expected =
new ContentData()
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("en", 2));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_remove_null_values_from_id_model_when_cleaning()
{
var input =
new ContentData()
.AddField("field1", null)
.AddField("field2",
new IdContentData()
.AddField(1, null)
.AddField(2,
new ContentFieldData()
.AddValue("en", 2)
.AddValue("it", null));
var actual = input.ToCleaned();
var expected =
new IdContentData()
.AddField(2,
new ContentFieldData()
.AddValue("en", 2));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_provide_invariant_from_first_language()
{
var expected =
new ContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));
var input =
new ContentData()
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("de", 2)
@ -255,20 +276,20 @@ namespace Squidex.Core.Contents
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_not_include_hidden_field()
{
var expected =
new ContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 5));
var input =
new ContentData()
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 5))
@ -278,6 +299,12 @@ namespace Squidex.Core.Contents
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 5));
Assert.Equal(expected, actual);
}
@ -285,7 +312,7 @@ namespace Squidex.Core.Contents
public void Should_return_original_when_no_language_preferences_defined()
{
var data =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1));
@ -297,7 +324,7 @@ namespace Squidex.Core.Contents
public void Should_return_flat_list_when_single_languages_specified()
{
var data =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("de", 1)
@ -333,7 +360,7 @@ namespace Squidex.Core.Contents
public void Should_return_flat_list_when_languages_specified()
{
var data =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("de", 1)
@ -362,10 +389,10 @@ namespace Squidex.Core.Contents
}
[Fact]
public void Should_merge_two_data()
public void Should_merge_two_name_models()
{
var lhs =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1))
@ -374,7 +401,7 @@ namespace Squidex.Core.Contents
.AddValue("de", 2));
var rhs =
new ContentData()
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("en", 3))
@ -383,7 +410,7 @@ namespace Squidex.Core.Contents
.AddValue("iv", 4));
var expected =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1))
@ -400,11 +427,50 @@ namespace Squidex.Core.Contents
Assert.Equal(expected, actual);
}
[Fact]
public void Should_merge_two_id_models()
{
var lhs =
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("iv", 1))
.AddField(2,
new ContentFieldData()
.AddValue("de", 2));
var rhs =
new IdContentData()
.AddField(2,
new ContentFieldData()
.AddValue("en", 3))
.AddField(3,
new ContentFieldData()
.AddValue("iv", 4));
var expected =
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddValue("iv", 1))
.AddField(2,
new ContentFieldData()
.AddValue("de", 2)
.AddValue("en", 3))
.AddField(3,
new ContentFieldData()
.AddValue("iv", 4));
var actual = lhs.MergeInto(rhs);
Assert.Equal(expected, actual);
}
[Fact]
public void Should_be_equal_when_data_have_same_structure()
{
var lhs =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 2))
@ -413,7 +479,7 @@ namespace Squidex.Core.Contents
.AddValue("iv", 2));
var rhs =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 2))
@ -430,7 +496,7 @@ namespace Squidex.Core.Contents
public void Should_not_be_equal_when_data_have_not_same_structure()
{
var lhs =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 2))
@ -439,7 +505,7 @@ namespace Squidex.Core.Contents
.AddValue("iv", 2));
var rhs =
new ContentData()
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", 2))
@ -451,5 +517,42 @@ namespace Squidex.Core.Contents
Assert.False(lhs.Equals((object)rhs));
Assert.NotEqual(lhs.GetHashCode(), rhs.GetHashCode());
}
[Fact]
public void Should_remove_ids()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var input =
new NamedContentData()
.AddField("assets1",
new ContentFieldData()
.AddValue("iv", new JArray(id1.ToString(), id2.ToString())));
var ids = input.GetReferencedIds(schema).ToArray();
Assert.Equal(new[] { id1, id2 }, ids);
}
[Fact]
public void Should_cleanup_deleted_ids()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var input =
new IdContentData()
.AddField(5,
new ContentFieldData()
.AddValue("iv", new JArray(id1.ToString(), id2.ToString())));
var actual = input.ToCleanedReferences(schema, new HashSet<Guid>(new[] { id2 }));
var cleanedValue = (JArray)actual[5]["iv"];
Assert.Equal(1, cleanedValue.Count);
Assert.Equal(id1.ToString(), cleanedValue[0]);
}
}
}

81
tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
@ -110,6 +111,86 @@ namespace Squidex.Core.Schemas
new[] { $"<FIELD> contains invalid asset '{assetId}'" });
}
[Fact]
public void Should_return_ids()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var result = sut.GetReferencedIds(CreateValue(id1, id2)).ToArray();
Assert.Equal(new[] { id1, id2 }, result);
}
[Fact]
public void Should_empty_list_for_referenced_ids_when_null()
{
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var result = sut.GetReferencedIds(null).ToArray();
Assert.Empty(result);
}
[Fact]
public void Should_empty_list_for_referenced_ids_when_other_type()
{
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var result = sut.GetReferencedIds("invalid").ToArray();
Assert.Empty(result);
}
[Fact]
public void Should_return_null_when_removing_references_from_null_array()
{
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var result = sut.RemoveDeletedReferences(null, null);
Assert.Null(result);
}
[Fact]
public void Should_return_null_when_removing_references_from_null_json_array()
{
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var result = sut.RemoveDeletedReferences(JValue.CreateNull(), null);
Assert.Null(result);
}
[Fact]
public void Should_remove_deleted_references()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var result = sut.RemoveDeletedReferences(CreateValue(id1, id2), new HashSet<Guid>(new[] { id2 }));
Assert.Equal(CreateValue(id1), result);
}
[Fact]
public void Should_return_same_token_when_removing_references_and_nothing_to_remove()
{
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant, assetTester.Object);
var token = CreateValue(id1, id2);
var result = sut.RemoveDeletedReferences(token, new HashSet<Guid>(new[] { Guid.NewGuid() }));
Assert.Same(token, result);
}
private static JToken CreateValue(params Guid[] ids)
{
return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids);

12
tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs

@ -34,7 +34,7 @@ namespace Squidex.Write.Contents
private readonly Mock<IAppProvider> appProvider = new Mock<IAppProvider>();
private readonly Mock<ISchemaEntity> schemaEntity = new Mock<ISchemaEntity>();
private readonly Mock<IAppEntity> appEntity = new Mock<IAppEntity>();
private readonly ContentData data = new ContentData().AddField("my-field", new ContentFieldData().SetValue(1));
private readonly NamedContentData data = new NamedContentData().AddField("my-field", new ContentFieldData().SetValue(1));
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.DE);
private readonly Guid contentId = Guid.NewGuid();
@ -60,7 +60,7 @@ namespace Squidex.Write.Contents
[Fact]
public async Task Create_should_throw_exception_if_data_is_not_valid()
{
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = new ContentData() });
var context = CreateContextForCommand(new CreateContent { ContentId = contentId, Data = new NamedContentData() });
await TestCreate(content, async _ =>
{
@ -78,7 +78,7 @@ namespace Squidex.Write.Contents
await sut.HandleAsync(context);
});
Assert.Equal(data, context.Result<EntityCreatedResult<ContentData>>().IdOrValue);
Assert.Equal(data, context.Result<EntityCreatedResult<NamedContentData>>().IdOrValue);
}
[Fact]
@ -86,7 +86,7 @@ namespace Squidex.Write.Contents
{
CreateContent();
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = new ContentData() });
var context = CreateContextForCommand(new UpdateContent { ContentId = contentId, Data = new NamedContentData() });
await TestUpdate(content, async _ =>
{
@ -112,7 +112,7 @@ namespace Squidex.Write.Contents
{
CreateContent();
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = new ContentData() });
var context = CreateContextForCommand(new PatchContent { ContentId = contentId, Data = new NamedContentData() });
await TestUpdate(content, async _ =>
{
@ -123,7 +123,7 @@ namespace Squidex.Write.Contents
[Fact]
public async Task Patch_should_update_domain_object()
{
var otherContent = new ContentData().AddField("my-field", new ContentFieldData().SetValue(3));
var otherContent = new NamedContentData().AddField("my-field", new ContentFieldData().SetValue(3));
CreateContent();

8
tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

@ -23,13 +23,13 @@ namespace Squidex.Write.Contents
public class ContentDomainObjectTests : HandlerTestBase<ContentDomainObject>
{
private readonly ContentDomainObject sut;
private readonly ContentData data =
new ContentData()
private readonly NamedContentData data =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1));
private readonly ContentData otherData =
new ContentData()
private readonly NamedContentData otherData =
new NamedContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));

Loading…
Cancel
Save