mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
26 changed files with 1273 additions and 733 deletions
@ -0,0 +1,74 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ConvertContent |
||||
|
{ |
||||
|
public static class ContentConverterFlat |
||||
|
{ |
||||
|
public static object ToFlatLanguageModel(this NamedContentData content, LanguagesConfig languagesConfig, IReadOnlyCollection<Language> languagePreferences = null) |
||||
|
{ |
||||
|
Guard.NotNull(languagesConfig, nameof(languagesConfig)); |
||||
|
|
||||
|
if (languagePreferences == null || languagePreferences.Count == 0) |
||||
|
{ |
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
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 content) |
||||
|
{ |
||||
|
var fieldData = fieldValue.Value; |
||||
|
|
||||
|
foreach (var language in languagePreferences) |
||||
|
{ |
||||
|
if (fieldData.TryGetValue(language, out var value) && value != null) |
||||
|
{ |
||||
|
result[fieldValue.Key] = value; |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static Dictionary<string, object> ToFlatten(this NamedContentData content) |
||||
|
{ |
||||
|
var result = new Dictionary<string, object>(); |
||||
|
|
||||
|
foreach (var fieldValue in content) |
||||
|
{ |
||||
|
var fieldData = fieldValue.Value; |
||||
|
|
||||
|
if (fieldData.Count == 1) |
||||
|
{ |
||||
|
result[fieldValue.Key] = fieldData.Values.First(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
result[fieldValue.Key] = fieldData; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,265 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// 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.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
|
||||
|
#pragma warning disable RECS0002 // Convert anonymous method to method group
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ConvertContent |
||||
|
{ |
||||
|
public static class FieldConverters |
||||
|
{ |
||||
|
public static FieldConverter ExcludeHidden() |
||||
|
{ |
||||
|
return (data, field) => |
||||
|
{ |
||||
|
return field.IsHidden ? null : data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter ExcludeChangedTypes() |
||||
|
{ |
||||
|
return (data, field) => |
||||
|
{ |
||||
|
var isValid = true; |
||||
|
|
||||
|
foreach (var value in data.Values) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (!value.IsNull()) |
||||
|
{ |
||||
|
JsonValueConverter.ConvertValue(field, value); |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
isValid = false; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!isValid) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter ResolveInvariant(LanguagesConfig languagesConfig) |
||||
|
{ |
||||
|
var codeForInvariant = InvariantPartitioning.Instance.Master.Key; |
||||
|
var codeForMasterLanguage = languagesConfig.Master.Language.Iso2Code; |
||||
|
|
||||
|
return (data, field) => |
||||
|
{ |
||||
|
if (field.Partitioning.Equals(Partitioning.Invariant)) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
if (data.TryGetValue(codeForInvariant, out var value)) |
||||
|
{ |
||||
|
result[codeForInvariant] = value; |
||||
|
} |
||||
|
else if (data.TryGetValue(codeForMasterLanguage, out value)) |
||||
|
{ |
||||
|
result[codeForInvariant] = value; |
||||
|
} |
||||
|
else if (data.Count > 0) |
||||
|
{ |
||||
|
result[codeForInvariant] = data.Values.First(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter ResolveLanguages(LanguagesConfig languagesConfig) |
||||
|
{ |
||||
|
var codeForInvariant = InvariantPartitioning.Instance.Master.Key; |
||||
|
var codeForMasterLanguage = languagesConfig.Master.Language.Iso2Code; |
||||
|
|
||||
|
return (data, field) => |
||||
|
{ |
||||
|
if (field.Partitioning.Equals(Partitioning.Language)) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
foreach (var languageConfig in languagesConfig) |
||||
|
{ |
||||
|
var languageCode = languageConfig.Key; |
||||
|
|
||||
|
if (data.TryGetValue(languageCode, out var value)) |
||||
|
{ |
||||
|
result[languageCode] = value; |
||||
|
} |
||||
|
else if (languageConfig == languagesConfig.Master && data.TryGetValue(codeForInvariant, out value)) |
||||
|
{ |
||||
|
result[languageCode] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter ResolveFallbackLanguages(LanguagesConfig languagesConfig) |
||||
|
{ |
||||
|
var master = languagesConfig.Master; |
||||
|
|
||||
|
return (data, field) => |
||||
|
{ |
||||
|
if (field.Partitioning.Equals(Partitioning.Language)) |
||||
|
{ |
||||
|
foreach (var languageConfig in languagesConfig) |
||||
|
{ |
||||
|
var languageCode = languageConfig.Key; |
||||
|
|
||||
|
if (!data.TryGetValue(languageCode, out var value)) |
||||
|
{ |
||||
|
var dataFound = false; |
||||
|
|
||||
|
foreach (var fallback in languageConfig.Fallback) |
||||
|
{ |
||||
|
if (data.TryGetValue(fallback, out value)) |
||||
|
{ |
||||
|
data[languageCode] = value; |
||||
|
dataFound = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!dataFound && languageConfig != master) |
||||
|
{ |
||||
|
if (data.TryGetValue(master.Language, out value)) |
||||
|
{ |
||||
|
data[languageCode] = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter FilterLanguages(LanguagesConfig languagesConfig, IEnumerable<Language> languages) |
||||
|
{ |
||||
|
if (languages == null) |
||||
|
{ |
||||
|
return (data, field) => data; |
||||
|
} |
||||
|
|
||||
|
var languageCodes = |
||||
|
new HashSet<string>( |
||||
|
languages.Select(x => x.Iso2Code).Where(x => languagesConfig.Contains(x)), |
||||
|
StringComparer.OrdinalIgnoreCase); |
||||
|
|
||||
|
if (languageCodes.Count == 0) |
||||
|
{ |
||||
|
return (data, field) => data; |
||||
|
} |
||||
|
|
||||
|
return (data, field) => |
||||
|
{ |
||||
|
if (field.Partitioning.Equals(Partitioning.Language)) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
foreach (var languageCode in languageCodes) |
||||
|
{ |
||||
|
if (data.TryGetValue(languageCode, out var value)) |
||||
|
{ |
||||
|
result[languageCode] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter DecodeJson() |
||||
|
{ |
||||
|
return (data, field) => |
||||
|
{ |
||||
|
if (field is JsonField) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
foreach (var partitionValue in data) |
||||
|
{ |
||||
|
if (partitionValue.Value.IsNull()) |
||||
|
{ |
||||
|
result[partitionValue.Key] = null; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString())); |
||||
|
|
||||
|
result[partitionValue.Key] = JToken.Parse(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static FieldConverter EncodeJson() |
||||
|
{ |
||||
|
return (data, field) => |
||||
|
{ |
||||
|
if (field is JsonField) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
foreach (var partitionValue in data) |
||||
|
{ |
||||
|
if (partitionValue.Value.IsNull()) |
||||
|
{ |
||||
|
result[partitionValue.Key] = null; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString())); |
||||
|
|
||||
|
result[partitionValue.Key] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds |
||||
|
{ |
||||
|
public static class FieldReferencesConverter |
||||
|
{ |
||||
|
public static FieldConverter CleanReferences(IEnumerable<Guid> deletedReferencedIds) |
||||
|
{ |
||||
|
var ids = new HashSet<Guid>(deletedReferencedIds); |
||||
|
|
||||
|
return (data, field) => |
||||
|
{ |
||||
|
foreach (var partitionValue in data.Where(x => !x.Value.IsNull()).ToList()) |
||||
|
{ |
||||
|
var newValue = field.CleanReferences(partitionValue.Value, ids); |
||||
|
|
||||
|
data[partitionValue.Key] = newValue; |
||||
|
} |
||||
|
|
||||
|
return data; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,145 +1,83 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
// All rights reserved. Licensed under the MIT license.
|
// All rights reserved. Licensed under the MIT license.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
using System; |
||||
using System.Collections.Concurrent; |
|
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
using System.Linq; |
|
||||
using System.Security.Claims; |
using System.Security.Claims; |
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Domain.Apps.Entities.Apps; |
using Squidex.Domain.Apps.Entities.Apps; |
||||
using Squidex.Domain.Apps.Entities.Assets; |
|
||||
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
|
||||
using Squidex.Infrastructure; |
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents |
namespace Squidex.Domain.Apps.Entities.Contents |
||||
{ |
{ |
||||
public class QueryContext |
public sealed class QueryContext : Cloneable<QueryContext> |
||||
{ |
{ |
||||
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>(); |
public ClaimsPrincipal User { get; private set; } |
||||
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>(); |
|
||||
private readonly IContentQueryService contentQuery; |
|
||||
private readonly IAssetRepository assetRepository; |
|
||||
private readonly IAppEntity app; |
|
||||
private readonly ClaimsPrincipal user; |
|
||||
|
|
||||
public QueryContext( |
|
||||
IAppEntity app, |
|
||||
IAssetRepository assetRepository, |
|
||||
IContentQueryService contentQuery, |
|
||||
ClaimsPrincipal user) |
|
||||
{ |
|
||||
Guard.NotNull(assetRepository, nameof(assetRepository)); |
|
||||
Guard.NotNull(contentQuery, nameof(contentQuery)); |
|
||||
Guard.NotNull(app, nameof(app)); |
|
||||
Guard.NotNull(user, nameof(user)); |
|
||||
|
|
||||
this.assetRepository = assetRepository; |
public IAppEntity App { get; private set; } |
||||
this.contentQuery = contentQuery; |
|
||||
|
|
||||
this.user = user; |
public IEnumerable<Language> Languages { get; private set; } |
||||
|
|
||||
this.app = app; |
public string SchemaIdOrName { get; private set; } |
||||
} |
|
||||
|
|
||||
public async Task<IAssetEntity> FindAssetAsync(Guid id) |
public bool Archived { get; private set; } |
||||
{ |
|
||||
var asset = cachedAssets.GetOrDefault(id); |
|
||||
|
|
||||
if (asset == null) |
|
||||
{ |
|
||||
asset = await assetRepository.FindAssetAsync(id); |
|
||||
|
|
||||
if (asset != null) |
public bool Flatten { get; private set; } |
||||
{ |
|
||||
cachedAssets[asset.Id] = asset; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return asset; |
private QueryContext() |
||||
|
{ |
||||
} |
} |
||||
|
|
||||
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id) |
public static QueryContext Create(IAppEntity app, ClaimsPrincipal user, IEnumerable<string> languageCodes = null) |
||||
{ |
{ |
||||
var content = cachedContents.GetOrDefault(id); |
var result = new QueryContext { App = app, User = user }; |
||||
|
|
||||
if (content == null) |
if (languageCodes != null) |
||||
{ |
{ |
||||
content = await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id); |
var languages = new List<Language>(); |
||||
|
|
||||
if (content != null) |
foreach (var iso2Code in languageCodes) |
||||
{ |
{ |
||||
cachedContents[content.Id] = content; |
if (Language.TryGetLanguage(iso2Code, out var language)) |
||||
|
{ |
||||
|
languages.Add(language); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
|
result.Languages = languages; |
||||
} |
} |
||||
|
|
||||
return content; |
return result; |
||||
} |
} |
||||
|
|
||||
public async Task<IResultList<IAssetEntity>> QueryAssetsAsync(string query) |
public QueryContext WithArchived(bool archived) |
||||
{ |
{ |
||||
var assets = await assetRepository.QueryAsync(app.Id, query); |
return Clone(c => c.Archived = archived); |
||||
|
|
||||
foreach (var asset in assets) |
|
||||
{ |
|
||||
cachedAssets[asset.Id] = asset; |
|
||||
} |
|
||||
|
|
||||
return assets; |
|
||||
} |
} |
||||
|
|
||||
public async Task<IResultList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query) |
public QueryContext WithFlatten(bool flatten) |
||||
{ |
{ |
||||
var result = await contentQuery.QueryAsync(app, schemaIdOrName, user, false, query); |
return Clone(c => c.Flatten = flatten); |
||||
|
|
||||
foreach (var content in result) |
|
||||
{ |
|
||||
cachedContents[content.Id] = content; |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
} |
||||
|
|
||||
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids) |
public QueryContext WithSchemaName(string name) |
||||
{ |
{ |
||||
Guard.NotNull(ids, nameof(ids)); |
return Clone(c => c.SchemaIdOrName = name); |
||||
|
|
||||
var notLoadedAssets = new HashSet<Guid>(ids.Where(id => !cachedAssets.ContainsKey(id))); |
|
||||
|
|
||||
if (notLoadedAssets.Count > 0) |
|
||||
{ |
|
||||
var assets = await assetRepository.QueryAsync(app.Id, notLoadedAssets); |
|
||||
|
|
||||
foreach (var asset in assets) |
|
||||
{ |
|
||||
cachedAssets[asset.Id] = asset; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ids.Select(cachedAssets.GetOrDefault).Where(x => x != null).ToList(); |
|
||||
} |
} |
||||
|
|
||||
public async Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, ICollection<Guid> ids) |
public QueryContext WithSchemaId(Guid id) |
||||
{ |
{ |
||||
Guard.NotNull(ids, nameof(ids)); |
return Clone(c => c.SchemaIdOrName = id.ToString()); |
||||
|
} |
||||
var notLoadedContents = new HashSet<Guid>(ids.Where(id => !cachedContents.ContainsKey(id))); |
|
||||
|
|
||||
if (notLoadedContents.Count > 0) |
|
||||
{ |
|
||||
var result = await contentQuery.QueryAsync(app, schemaId.ToString(), user, false, notLoadedContents); |
|
||||
|
|
||||
foreach (var content in result) |
|
||||
{ |
|
||||
cachedContents[content.Id] = content; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ids.Select(cachedContents.GetOrDefault).Where(x => x != null).ToList(); |
public bool IsFrontendClient |
||||
|
{ |
||||
|
get { return User.IsInClient("squidex-frontend"); } |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,136 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Assets; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents |
||||
|
{ |
||||
|
public class QueryExecutionContext |
||||
|
{ |
||||
|
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>(); |
||||
|
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>(); |
||||
|
private readonly IContentQueryService contentQuery; |
||||
|
private readonly IAssetRepository assetRepository; |
||||
|
private readonly QueryContext context; |
||||
|
|
||||
|
public QueryExecutionContext(QueryContext context, |
||||
|
IAssetRepository assetRepository, |
||||
|
IContentQueryService contentQuery) |
||||
|
{ |
||||
|
Guard.NotNull(assetRepository, nameof(assetRepository)); |
||||
|
Guard.NotNull(contentQuery, nameof(contentQuery)); |
||||
|
Guard.NotNull(context, nameof(context)); |
||||
|
|
||||
|
this.assetRepository = assetRepository; |
||||
|
this.contentQuery = contentQuery; |
||||
|
this.context = context; |
||||
|
} |
||||
|
|
||||
|
public async Task<IAssetEntity> FindAssetAsync(Guid id) |
||||
|
{ |
||||
|
var asset = cachedAssets.GetOrDefault(id); |
||||
|
|
||||
|
if (asset == null) |
||||
|
{ |
||||
|
asset = await assetRepository.FindAssetAsync(id); |
||||
|
|
||||
|
if (asset != null) |
||||
|
{ |
||||
|
cachedAssets[asset.Id] = asset; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return asset; |
||||
|
} |
||||
|
|
||||
|
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id) |
||||
|
{ |
||||
|
var content = cachedContents.GetOrDefault(id); |
||||
|
|
||||
|
if (content == null) |
||||
|
{ |
||||
|
content = await contentQuery.FindContentAsync(context.WithSchemaId(schemaId), id); |
||||
|
|
||||
|
if (content != null) |
||||
|
{ |
||||
|
cachedContents[content.Id] = content; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
public async Task<IResultList<IAssetEntity>> QueryAssetsAsync(string query) |
||||
|
{ |
||||
|
var assets = await assetRepository.QueryAsync(context.App.Id, query); |
||||
|
|
||||
|
foreach (var asset in assets) |
||||
|
{ |
||||
|
cachedAssets[asset.Id] = asset; |
||||
|
} |
||||
|
|
||||
|
return assets; |
||||
|
} |
||||
|
|
||||
|
public async Task<IResultList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query) |
||||
|
{ |
||||
|
var result = await contentQuery.QueryAsync(context.WithSchemaName(schemaIdOrName), query); |
||||
|
|
||||
|
foreach (var content in result) |
||||
|
{ |
||||
|
cachedContents[content.Id] = content; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids) |
||||
|
{ |
||||
|
Guard.NotNull(ids, nameof(ids)); |
||||
|
|
||||
|
var notLoadedAssets = new HashSet<Guid>(ids.Where(id => !cachedAssets.ContainsKey(id))); |
||||
|
|
||||
|
if (notLoadedAssets.Count > 0) |
||||
|
{ |
||||
|
var assets = await assetRepository.QueryAsync(context.App.Id, notLoadedAssets); |
||||
|
|
||||
|
foreach (var asset in assets) |
||||
|
{ |
||||
|
cachedAssets[asset.Id] = asset; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ids.Select(cachedAssets.GetOrDefault).Where(x => x != null).ToList(); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, ICollection<Guid> ids) |
||||
|
{ |
||||
|
Guard.NotNull(ids, nameof(ids)); |
||||
|
|
||||
|
var notLoadedContents = new HashSet<Guid>(ids.Where(id => !cachedContents.ContainsKey(id))); |
||||
|
|
||||
|
if (notLoadedContents.Count > 0) |
||||
|
{ |
||||
|
var result = await contentQuery.QueryAsync(context.WithSchemaId(schemaId), notLoadedContents); |
||||
|
|
||||
|
foreach (var content in result) |
||||
|
{ |
||||
|
cachedContents[content.Id] = content; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ids.Select(cachedContents.GetOrDefault).Where(x => x != null).ToList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,153 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable xUnit2013 // Do not use equality check to check for collection size.
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Operations.ConvertContent |
||||
|
{ |
||||
|
public class ContentConversionFlatTests |
||||
|
{ |
||||
|
private readonly Schema schema; |
||||
|
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); |
||||
|
|
||||
|
public ContentConversionFlatTests() |
||||
|
{ |
||||
|
schema = |
||||
|
new Schema("my-schema") |
||||
|
.AddField(new NumberField(1, "field1", Partitioning.Language)) |
||||
|
.AddField(new NumberField(2, "field2", Partitioning.Invariant)) |
||||
|
.AddField(new NumberField(3, "field3", Partitioning.Invariant)) |
||||
|
.AddField(new AssetsField(5, "assets1", Partitioning.Invariant)) |
||||
|
.AddField(new AssetsField(6, "assets2", Partitioning.Invariant)) |
||||
|
.AddField(new JsonField(4, "json", Partitioning.Language)) |
||||
|
.HideField(3); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_original_when_no_language_preferences_defined() |
||||
|
{ |
||||
|
var data = |
||||
|
new NamedContentData() |
||||
|
.AddField("field1", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 1)); |
||||
|
|
||||
|
Assert.Same(data, data.ToFlatLanguageModel(languagesConfig)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_flatten_value() |
||||
|
{ |
||||
|
var data = |
||||
|
new NamedContentData() |
||||
|
.AddField("field1", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", 1) |
||||
|
.AddValue("en", 2)) |
||||
|
.AddField("field2", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", null) |
||||
|
.AddValue("en", 4)) |
||||
|
.AddField("field3", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", 6)) |
||||
|
.AddField("field4", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("it", 7)); |
||||
|
|
||||
|
var output = data.ToFlatten(); |
||||
|
|
||||
|
var expected = new Dictionary<string, object> |
||||
|
{ |
||||
|
{ "field1", new ContentFieldData().AddValue("de", 1).AddValue("en", 2) }, |
||||
|
{ "field2", new ContentFieldData().AddValue("de", null).AddValue("en", 4) }, |
||||
|
{ "field3", (JValue)6 }, |
||||
|
{ "field4", (JValue)7 } |
||||
|
}; |
||||
|
|
||||
|
Assert.True(expected.EqualsDictionary(output)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_flat_list_when_single_languages_specified() |
||||
|
{ |
||||
|
var data = |
||||
|
new NamedContentData() |
||||
|
.AddField("field1", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", 1) |
||||
|
.AddValue("en", 2)) |
||||
|
.AddField("field2", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", null) |
||||
|
.AddValue("en", 4)) |
||||
|
.AddField("field3", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", 6)) |
||||
|
.AddField("field4", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("it", 7)); |
||||
|
|
||||
|
var fallbackConfig = |
||||
|
LanguagesConfig.Build( |
||||
|
new LanguageConfig(Language.EN), |
||||
|
new LanguageConfig(Language.DE, false, Language.EN)); |
||||
|
|
||||
|
var output = (Dictionary<string, JToken>)data.ToFlatLanguageModel(fallbackConfig, new List<Language> { Language.DE }); |
||||
|
|
||||
|
var expected = new Dictionary<string, JToken> |
||||
|
{ |
||||
|
{ "field1", 1 }, |
||||
|
{ "field2", 4 }, |
||||
|
{ "field3", 6 } |
||||
|
}; |
||||
|
|
||||
|
Assert.True(expected.EqualsDictionary(output)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_flat_list_when_languages_specified() |
||||
|
{ |
||||
|
var data = |
||||
|
new NamedContentData() |
||||
|
.AddField("field1", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", 1) |
||||
|
.AddValue("en", 2)) |
||||
|
.AddField("field2", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", null) |
||||
|
.AddValue("en", 4)) |
||||
|
.AddField("field3", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", 6)) |
||||
|
.AddField("field4", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("it", 7)); |
||||
|
|
||||
|
var output = (Dictionary<string, JToken>)data.ToFlatLanguageModel(languagesConfig, new List<Language> { Language.DE, Language.EN }); |
||||
|
|
||||
|
var expected = new Dictionary<string, JToken> |
||||
|
{ |
||||
|
{ "field1", 1 }, |
||||
|
{ "field2", 4 }, |
||||
|
{ "field3", 6 } |
||||
|
}; |
||||
|
|
||||
|
Assert.True(expected.EqualsDictionary(output)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,325 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Operations.ConvertContent |
||||
|
{ |
||||
|
public class FieldConvertersTests |
||||
|
{ |
||||
|
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); |
||||
|
private readonly StringField stringLanguageField = new StringField(1, "1", Partitioning.Language); |
||||
|
private readonly StringField stringInvariantField = new StringField(1, "1", Partitioning.Invariant); |
||||
|
private readonly NumberField numberField = new NumberField(1, "1", Partitioning.Invariant); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_encode_json_values() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", null) |
||||
|
.AddValue("de", JToken.FromObject(new { Value = 1 })); |
||||
|
|
||||
|
var result = FieldConverters.EncodeJson()(source, new JsonField(1, "1", Partitioning.Invariant)); |
||||
|
|
||||
|
Assert.Null(result["en"]); |
||||
|
Assert.True(result["de"].Type == JTokenType.String); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_encoding_non_json_field() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", null); |
||||
|
|
||||
|
var result = FieldConverters.EncodeJson()(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_decode_json_values() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", null) |
||||
|
.AddValue("de", "e30="); |
||||
|
|
||||
|
var result = FieldConverters.DecodeJson()(source, new JsonField(1, "1", Partitioning.Invariant)); |
||||
|
|
||||
|
Assert.Null(result["en"]); |
||||
|
Assert.True(result["de"] is JObject); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_all_values_are_valid() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", null) |
||||
|
.AddValue("de", 1); |
||||
|
|
||||
|
var result = FieldConverters.ExcludeChangedTypes()(source, numberField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_null_values_if_all_value_is_invalid() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "EN") |
||||
|
.AddValue("de", 0); |
||||
|
|
||||
|
var result = FieldConverters.ExcludeChangedTypes()(source, numberField); |
||||
|
|
||||
|
Assert.Null(result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_decoding_non_json_field() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", null); |
||||
|
|
||||
|
var result = FieldConverters.DecodeJson()(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_field_not_hidden() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.ExcludeHidden()(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_null_values_if_field_hidden() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.ExcludeHidden()(source, stringLanguageField.Hide()); |
||||
|
|
||||
|
Assert.Null(result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_resolve_languages_and_cleanup_old_languages() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "EN") |
||||
|
.AddValue("it", "IT"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "EN"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveLanguages(languagesConfig)(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_resolve_languages_and_resolve_master_language_from_invariant() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "A") |
||||
|
.AddValue("it", "B"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "A"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveLanguages(languagesConfig)(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_resolving_languages_from_invariant_field() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.ResolveLanguages(languagesConfig)(source, stringInvariantField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_resolve_invariant_and_use_direct_value() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "A") |
||||
|
.AddValue("it", "B"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "A"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveInvariant(languagesConfig)(source, stringInvariantField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_resolve_invariant_and_resolve_invariant_from_master_language() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", "DE") |
||||
|
.AddValue("en", "EN"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "EN"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveInvariant(languagesConfig)(source, stringInvariantField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_resolve_invariant_and_resolve_invariant_from_first_language() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", "DE") |
||||
|
.AddValue("it", "IT"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "DE"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveInvariant(languagesConfig)(source, stringInvariantField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_resolving_invariant_from_language_field() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.ResolveInvariant(languagesConfig)(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_language_from_fallback_if_found() |
||||
|
{ |
||||
|
var config_1 = languagesConfig.Set(new LanguageConfig(Language.IT)); |
||||
|
var config_2 = config_1.Set(new LanguageConfig(Language.ES, false, Language.IT)); |
||||
|
|
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "EN") |
||||
|
.AddValue("it", "IT"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "EN") |
||||
|
.AddValue("de", "EN") |
||||
|
.AddValue("it", "IT") |
||||
|
.AddValue("es", "IT"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveFallbackLanguages(config_2)(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_return_value_if_master_is_missing() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", "DE"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", "DE"); |
||||
|
|
||||
|
var result = FieldConverters.ResolveFallbackLanguages(languagesConfig)(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_filter_languages() |
||||
|
{ |
||||
|
var source = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("en", "EN") |
||||
|
.AddValue("de", "DE"); |
||||
|
|
||||
|
var expected = |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", "DE"); |
||||
|
|
||||
|
var result = FieldConverters.FilterLanguages(languagesConfig, new[] { Language.DE })(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_resolving_fallback_languages_from_invariant_field() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.ResolveFallbackLanguages(languagesConfig)(source, stringInvariantField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_filtered_languages_are_invalid() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.FilterLanguages(languagesConfig, new[] { Language.CA })(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_filtered_languages_is_empty() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.FilterLanguages(languagesConfig, Enumerable.Empty<Language>())(source, stringLanguageField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_values_if_filtering_languages_from_invariant_field() |
||||
|
{ |
||||
|
var source = new ContentFieldData(); |
||||
|
|
||||
|
var result = FieldConverters.FilterLanguages(languagesConfig, null)(source, stringInvariantField); |
||||
|
|
||||
|
Assert.Same(source, result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue