From 5d1b13e0375cb7e5c3dcc1eb3824376c8b899c9c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 21 May 2018 12:05:00 +0200 Subject: [PATCH] Queries improved. --- .../ConvertContent/ContentConverter.cs | 178 +++------ .../ConvertContent/ContentConverterFlat.cs | 74 ++++ .../ConvertContent/FieldConverters.cs | 265 ++++++++++++++ .../ContentReferencesExtensions.cs | 27 -- .../FieldReferencesConverter.cs | 35 ++ .../ExtractReferenceIds/ReferencesCleaner.cs | 1 + .../Contents/Extensions.cs | 9 +- .../Contents/MongoContentEntity.cs | 4 +- .../MongoContentRepository_SnapshotStore.cs | 5 +- .../Contents/ContentQueryService.cs | 140 ++++--- .../Contents/GraphQL/CachingGraphQLService.cs | 9 +- .../GraphQL/GraphQLExecutionContext.cs | 11 +- .../Contents/GraphQL/IGraphQLService.cs | 4 +- .../Contents/IContentQueryService.cs | 10 +- .../Contents/QueryContext.cs | 134 ++----- .../Contents/QueryExecutionContext.cs | 136 +++++++ .../Contents/ContentsController.cs | 43 ++- .../Controllers/Contents/Models/ContentDto.cs | 15 +- .../ContentConversionFlatTests.cs | 153 ++++++++ .../ConvertContent/ContentConversionTests.cs | 344 ++---------------- .../ConvertContent/FieldConvertersTests.cs | 325 +++++++++++++++++ .../ReferenceExtractionTests.cs | 3 +- .../Contents/ContentQueryServiceTests.cs | 23 +- .../Contents/GraphQL/GraphQLMutationTests.cs | 16 +- .../Contents/GraphQL/GraphQLQueriesTests.cs | 39 +- .../Contents/GraphQL/GraphQLTestBase.cs | 3 + 26 files changed, 1273 insertions(+), 733 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs index f6c2e0c47..e450d470d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs @@ -5,23 +5,17 @@ // 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; namespace Squidex.Domain.Apps.Core.ConvertContent { + public delegate ContentFieldData FieldConverter(ContentFieldData data, Field field); + public static class ContentConverter { - public static NamedContentData ToNameModel(this IdContentData source, Schema schema, bool decodeJsonField) + public static NamedContentData ToNameModel(this IdContentData source, Schema schema, params FieldConverter[] converters) { Guard.NotNull(schema, nameof(schema)); @@ -34,36 +28,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent continue; } - if (decodeJsonField && field is JsonField) - { - var encodedValue = new ContentFieldData(); - - foreach (var partitionValue in fieldValue.Value) - { - if (partitionValue.Value.IsNull()) - { - encodedValue[partitionValue.Key] = null; - } - else - { - var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString())); - - encodedValue[partitionValue.Key] = JToken.Parse(value); - } - } + var fieldData = Convert(fieldValue.Value, field, converters); - result[field.Name] = encodedValue; - } - else + if (fieldData != null) { - result[field.Name] = fieldValue.Value; + result[field.Name] = fieldData; } } return result; } - public static IdContentData ToIdModel(this NamedContentData content, Schema schema, bool encodeJsonField) + public static IdContentData ToIdModel(this NamedContentData content, Schema schema, params FieldConverter[] converters) { Guard.NotNull(schema, nameof(schema)); @@ -76,153 +52,81 @@ namespace Squidex.Domain.Apps.Core.ConvertContent continue; } - var fieldId = field.Id; + var fieldData = Convert(fieldValue.Value, field, converters); - if (encodeJsonField && field is JsonField) + if (fieldData != null) { - var encodedValue = new ContentFieldData(); - - foreach (var partitionValue in fieldValue.Value) - { - if (partitionValue.Value.IsNull()) - { - encodedValue[partitionValue.Key] = null; - } - else - { - var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString())); - - encodedValue[partitionValue.Key] = value; - } - } - - result[fieldId] = encodedValue; - } - else - { - result[fieldId] = fieldValue.Value; + result[field.Id] = fieldData; } } return result; } - public static NamedContentData ToApiModel(this NamedContentData content, Schema schema, LanguagesConfig languagesConfig, bool excludeHidden = true, bool checkTypeCompatibility = false) + public static IdContentData Convert(this IdContentData content, Schema schema, params FieldConverter[] converters) { 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(); + var result = new IdContentData(); foreach (var fieldValue in content) { - if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field) || (excludeHidden && field.IsHidden)) + if (!schema.FieldsById.TryGetValue(fieldValue.Key, out var field)) { continue; } - if (checkTypeCompatibility) - { - var isValid = true; - - foreach (var value in fieldValue.Value.Values) - { - try - { - if (!value.IsNull()) - { - JsonValueConverter.ConvertValue(field, value); - } - } - catch - { - isValid = false; - break; - } - } - - if (!isValid) - { - continue; - } - } - - var fieldResult = new ContentFieldData(); - var fieldValues = fieldValue.Value; + var fieldData = Convert(fieldValue.Value, field, converters); - if (field.Partitioning.Equals(Partitioning.Language)) + if (fieldData != null) { - foreach (var languageConfig in languagesConfig) - { - var languageCode = languageConfig.Key; - - if (fieldValues.TryGetValue(languageCode, out var value)) - { - fieldResult.Add(languageCode, value); - } - else if (languageConfig == languagesConfig.Master && fieldValues.TryGetValue(codeForInvariant, out value)) - { - fieldResult.Add(languageCode, value); - } - } + result[field.Id] = fieldData; } - else - { - if (fieldValues.TryGetValue(codeForInvariant, out var value)) - { - fieldResult.Add(codeForInvariant, value); - } - else if (fieldValues.TryGetValue(codeForMasterLanguage, out value)) - { - fieldResult.Add(codeForInvariant, value); - } - else if (fieldValues.Count > 0) - { - fieldResult.Add(codeForInvariant, fieldValues.Values.First()); - } - } - - result.Add(field.Name, fieldResult); } return result; } - public static object ToLanguageModel(this NamedContentData content, LanguagesConfig languagesConfig, IReadOnlyCollection languagePreferences = null) + public static NamedContentData Convert(this NamedContentData content, Schema schema, params FieldConverter[] converters) { - Guard.NotNull(languagesConfig, nameof(languagesConfig)); + Guard.NotNull(schema, nameof(schema)); - if (languagePreferences == null || languagePreferences.Count == 0) - { - return content; - } + var result = new NamedContentData(); - if (languagePreferences.Count == 1 && languagesConfig.TryGetConfig(languagePreferences.First(), out var languageConfig)) + foreach (var fieldValue in content) { - languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList(); + if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field)) + { + continue; + } + + var fieldData = Convert(fieldValue.Value, field, converters); + + if (fieldData != null) + { + result[field.Name] = fieldData; + } } - var result = new Dictionary(); + return result; + } - foreach (var fieldValue in content) + private static ContentFieldData Convert(ContentFieldData fieldData, Field field, FieldConverter[] converters) + { + if (converters != null) { - var fieldValues = fieldValue.Value; - - foreach (var language in languagePreferences) + foreach (var converter in converters) { - if (fieldValues.TryGetValue(language, out var value) && value != null) - { - result[fieldValue.Key] = value; + fieldData = converter(fieldData, field); + if (fieldData == null) + { break; } } } - return result; + return fieldData; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs new file mode 100644 index 000000000..5bff83392 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs @@ -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 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(); + + 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 ToFlatten(this NamedContentData content) + { + var result = new Dictionary(); + + 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; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs new file mode 100644 index 000000000..944d6c579 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs @@ -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 languages) + { + if (languages == null) + { + return (data, field) => data; + } + + var languageCodes = + new HashSet( + 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; + }; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs index 8eb673ae6..9d4fddf8d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs @@ -17,33 +17,6 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public static class ContentReferencesExtensions { - public static IdContentData ToCleanedReferences(this IdContentData source, Schema schema, ISet deletedReferencedIds) - { - Guard.NotNull(schema, nameof(schema)); - Guard.NotNull(deletedReferencedIds, nameof(deletedReferencedIds)); - - var result = new IdContentData(source); - - foreach (var field in schema.Fields) - { - var fieldData = source.GetOrDefault(field.Id); - - if (fieldData == null) - { - continue; - } - - foreach (var partitionValue in fieldData.Where(x => !x.Value.IsNull()).ToList()) - { - var newValue = field.CleanReferences(partitionValue.Value, deletedReferencedIds); - - fieldData[partitionValue.Key] = newValue; - } - } - - return result; - } - public static IEnumerable GetReferencedIds(this IdContentData source, Schema schema) { Guard.NotNull(schema, nameof(schema)); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs new file mode 100644 index 000000000..8f1a751cc --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs @@ -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 deletedReferencedIds) + { + var ids = new HashSet(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; + }; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs index 834bc6a87..e45771ea8 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs @@ -17,6 +17,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds public static class ReferencesCleaner { private static readonly List EmptyIds = new List(); + public static JToken CleanReferences(this Field field, JToken value, ISet oldReferences) { if ((field is AssetsField || field is ReferencesField) && !value.IsNull()) diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs index d3d48a048..b4917e217 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs @@ -26,9 +26,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return data.GetReferencedIds(schema).ToList(); } - public static NamedContentData ToData(this IdContentData idData, Schema schema, List deletedIds) + public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List deletedIds) { - return idData.ToCleanedReferences(schema, new HashSet(deletedIds)).ToNameModel(schema, true); + return result.ToNameModel(schema, FieldConverters.DecodeJson(), FieldReferencesConverter.CleanReferences(deletedIds)); + } + + public static IdContentData ToMongoModel(this NamedContentData result, Schema schema) + { + return result.ToIdModel(schema, FieldConverters.EncodeJson()); } public static string ToFullText(this ContentData data) diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index 568015a14..b758c7919 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -126,11 +126,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public void ParseData(Schema schema) { - data = DataByIds.ToData(schema, ReferencedIdsDeleted); + data = DataByIds.FromMongoModel(schema, ReferencedIdsDeleted); if (DataDraftByIds != null) { - dataDraft = DataDraftByIds.ToData(schema, ReferencedIdsDeleted); + dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted); } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index 5766a023b..e91e02385 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs @@ -8,7 +8,6 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -39,12 +38,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id); - var idData = value.Data.ToIdModel(schema.SchemaDef, true); + var idData = value.Data.ToMongoModel(schema.SchemaDef); var content = SimpleMapper.Map(value, new MongoContentEntity { DataByIds = idData, - DataDraftByIds = value.DataDraft?.ToIdModel(schema.SchemaDef, true), + DataDraftByIds = value.DataDraft?.ToMongoModel(schema.SchemaDef), IsDeleted = value.IsDeleted, IndexedAppId = value.AppId.Id, IndexedSchemaId = value.SchemaId.Id, diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 70f0da81a..0e0a3c4af 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -8,21 +8,18 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.OData; using Microsoft.OData.UriParser; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Reflection; -using Squidex.Infrastructure.Security; namespace Squidex.Domain.Apps.Entities.Contents { @@ -58,108 +55,90 @@ namespace Squidex.Domain.Apps.Entities.Contents this.modelBuilder = modelBuilder; } - public Task ThrowIfSchemaNotExistsAsync(IAppEntity app, string schemaIdOrName) + public Task ThrowIfSchemaNotExistsAsync(QueryContext context) { - return GetSchemaAsync(app, schemaIdOrName); + return GetSchemaAsync(context); } - public async Task FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = -1) + public async Task FindContentAsync(QueryContext context, Guid id, long version = -1) { - Guard.NotNull(app, nameof(app)); - Guard.NotNull(user, nameof(user)); + Guard.NotNull(context, nameof(context)); Guard.NotEmpty(id, nameof(id)); - Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); - var schema = await GetSchemaAsync(app, schemaIdOrName); + var schema = await GetSchemaAsync(context); using (Profiler.TraceMethod()) { var isVersioned = version > EtagVersion.Empty; - var isFrontend = IsFrontendClient(user); - var parsedStatus = isFrontend ? StatusAll : StatusPublished; + var parsedStatus = context.IsFrontendClient ? StatusAll : StatusPublished; var content = isVersioned ? await FindContentByVersionAsync(id, version) : - await FindContentAsync(app, id, parsedStatus, schema); + await FindContentAsync(context, id, parsedStatus, schema); - if (content == null || (content.Status != Status.Published && !isFrontend) || content.SchemaId.Id != schema.Id) + if (content == null || (content.Status != Status.Published && !context.IsFrontendClient) || content.SchemaId.Id != schema.Id) { throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity)); } - return TransformContent(app, schema, user, content, isFrontend, isVersioned); + return TransformContent(context, schema, true, content); } } - public async Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) + public async Task> QueryAsync(QueryContext context, string query) { - Guard.NotNull(app, nameof(app)); - Guard.NotNull(user, nameof(user)); - Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); + Guard.NotNull(context, nameof(context)); - var schema = await GetSchemaAsync(app, schemaIdOrName); + var schema = await GetSchemaAsync(context); using (Profiler.TraceMethod("QueryAsyncByQuery")) { - var isFrontend = IsFrontendClient(user); + var parsedQuery = ParseQuery(context, query, schema); + var parsedStatus = ParseStatus(context); - var parsedQuery = ParseQuery(app, query, schema); - var parsedStatus = ParseStatus(isFrontend, archived); + var contents = await contentRepository.QueryAsync(context.App, schema, parsedStatus, parsedQuery); - var contents = await contentRepository.QueryAsync(app, schema, parsedStatus, parsedQuery); - - return TransformContents(app, schema, user, contents, false, isFrontend); + return TransformContents(context, schema, true, contents); } } - public async Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids) + public async Task> QueryAsync(QueryContext context, HashSet ids) { + Guard.NotNull(context, nameof(context)); Guard.NotNull(ids, nameof(ids)); - Guard.NotNull(app, nameof(app)); - Guard.NotNull(user, nameof(user)); - Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); - var schema = await GetSchemaAsync(app, schemaIdOrName); + var schema = await GetSchemaAsync(context); using (Profiler.TraceMethod("QueryAsyncByIds")) { - var isFrontend = IsFrontendClient(user); - - var parsedStatus = ParseStatus(isFrontend, archived); + var parsedStatus = ParseStatus(context); - var contents = await contentRepository.QueryAsync(app, schema, parsedStatus, ids); + var contents = await contentRepository.QueryAsync(context.App, schema, parsedStatus, ids); - return TransformContents(app, schema, user, contents, false, isFrontend); + return TransformContents(context, schema, false, contents); } } - private IContentEntity TransformContent(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, - IContentEntity content, - bool isFrontend, - bool isVersioned) + private IContentEntity TransformContent(QueryContext context, ISchemaEntity schema, bool checkType, IContentEntity content) { - return TransformContents(app, schema, user, Enumerable.Repeat(content, 1), isVersioned, isFrontend).FirstOrDefault(); + return TransformContents(context, schema, checkType, Enumerable.Repeat(content, 1)).FirstOrDefault(); } - private IResultList TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, - IResultList contents, - bool isTypeChecking, - bool isFrontendClient) + private IResultList TransformContents(QueryContext context, ISchemaEntity schema, bool checkType, IResultList contents) { - var transformed = TransformContents(app, schema, user, (IEnumerable)contents, isTypeChecking, isFrontendClient); + var transformed = TransformContents(context, schema, checkType, (IEnumerable)contents); return ResultList.Create(transformed, contents.Total); } - private IEnumerable TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, - IEnumerable contents, - bool isTypeChecking, - bool isFrontendClient) + private IEnumerable TransformContents(QueryContext context, ISchemaEntity schema, bool checkType, IEnumerable contents) { using (Profiler.TraceMethod()) { + var converters = GenerateConverters(context, checkType).ToArray(); + var scriptText = schema.ScriptQuery; var isScripting = !string.IsNullOrWhiteSpace(scriptText); @@ -170,17 +149,17 @@ namespace Squidex.Domain.Apps.Entities.Contents if (result.Data != null) { - if (!isFrontendClient && isScripting) + if (!context.IsFrontendClient && isScripting) { - result.Data = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText); + result.Data = scriptEngine.Transform(new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id }, scriptText); } - result.Data = result.Data.ToApiModel(schema.SchemaDef, app.LanguagesConfig, isFrontendClient, isTypeChecking); + result.Data = result.Data.Convert(schema.SchemaDef, converters); } if (result.DataDraft != null) { - result.DataDraft = result.DataDraft.ToApiModel(schema.SchemaDef, app.LanguagesConfig, isFrontendClient, isTypeChecking); + result.DataDraft = result.DataDraft.Convert(schema.SchemaDef, converters); } yield return result; @@ -188,13 +167,35 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private ODataUriParser ParseQuery(IAppEntity app, string query, ISchemaEntity schema) + private IEnumerable GenerateConverters(QueryContext context, bool checkType) + { + if (!context.IsFrontendClient) + { + yield return FieldConverters.ExcludeHidden(); + } + + if (checkType) + { + yield return FieldConverters.ExcludeChangedTypes(); + } + + yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig); + yield return FieldConverters.ResolveLanguages(context.App.LanguagesConfig); + + if (!context.IsFrontendClient) + { + yield return FieldConverters.ResolveFallbackLanguages(context.App.LanguagesConfig); + yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, context.Languages); + } + } + + private ODataUriParser ParseQuery(QueryContext context, string query, ISchemaEntity schema) { using (Profiler.TraceMethod()) { try { - var model = modelBuilder.BuildEdmModel(schema, app); + var model = modelBuilder.BuildEdmModel(schema, context.App); return model.ParseQuery(query); } @@ -205,35 +206,33 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - public async Task GetSchemaAsync(IAppEntity app, string schemaIdOrName) + public async Task GetSchemaAsync(QueryContext context) { - Guard.NotNull(app, nameof(app)); - ISchemaEntity schema = null; - if (Guid.TryParse(schemaIdOrName, out var id)) + if (Guid.TryParse(context.SchemaIdOrName, out var id)) { - schema = await appProvider.GetSchemaAsync(app.Id, id); + schema = await appProvider.GetSchemaAsync(context.App.Id, id); } if (schema == null) { - schema = await appProvider.GetSchemaAsync(app.Id, schemaIdOrName); + schema = await appProvider.GetSchemaAsync(context.App.Id, context.SchemaIdOrName); } if (schema == null) { - throw new DomainObjectNotFoundException(schemaIdOrName, typeof(ISchemaEntity)); + throw new DomainObjectNotFoundException(context.SchemaIdOrName, typeof(ISchemaEntity)); } return schema; } - private static Status[] ParseStatus(bool isFrontendClient, bool archived) + private static Status[] ParseStatus(QueryContext context) { - if (isFrontendClient) + if (context.IsFrontendClient) { - if (archived) + if (context.Archived) { return StatusArchived; } @@ -249,14 +248,9 @@ namespace Squidex.Domain.Apps.Entities.Contents return contentVersionLoader.LoadAsync(id, version); } - private Task FindContentAsync(IAppEntity app, Guid id, Status[] status, ISchemaEntity schema) - { - return contentRepository.FindContentAsync(app, schema, status, id); - } - - private static bool IsFrontendClient(ClaimsPrincipal user) + private Task FindContentAsync(QueryContext context, Guid id, Status[] status, ISchemaEntity schema) { - return user.IsInClient("squidex-frontend"); + return contentRepository.FindContentAsync(context.App, schema, status, id); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index eb3d9f315..56f8bf48c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -7,7 +7,6 @@ using System; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Squidex.Domain.Apps.Entities.Apps; @@ -47,9 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL this.urlGenerator = urlGenerator; } - public async Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query) + public async Task<(object Data, object[] Errors)> QueryAsync(QueryContext context, GraphQLQuery query) { - Guard.NotNull(app, nameof(app)); + Guard.NotNull(context, nameof(context)); Guard.NotNull(query, nameof(query)); if (string.IsNullOrWhiteSpace(query.Query)) @@ -57,9 +56,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return (new object(), new object[0]); } - var modelContext = await GetModelAsync(app); + var modelContext = await GetModelAsync(context.App); - var ctx = new GraphQLExecutionContext(app, assetRepository, commandBus, contentQuery, user, urlGenerator); + var ctx = new GraphQLExecutionContext(context, assetRepository, commandBus, contentQuery, urlGenerator); return await modelContext.ExecuteAsync(ctx, query); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index f73f3ab6d..8456884eb 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs @@ -7,25 +7,26 @@ using System; using System.Collections.Generic; -using System.Security.Claims; using System.Threading.Tasks; using Newtonsoft.Json.Linq; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public sealed class GraphQLExecutionContext : QueryContext + public sealed class GraphQLExecutionContext : QueryExecutionContext { public ICommandBus CommandBus { get; } public IGraphQLUrlGenerator UrlGenerator { get; } - public GraphQLExecutionContext(IAppEntity app, IAssetRepository assetRepository, ICommandBus commandBus, IContentQueryService contentQuery, ClaimsPrincipal user, + public GraphQLExecutionContext(QueryContext context, + IAssetRepository assetRepository, + ICommandBus commandBus, + IContentQueryService contentQuery, IGraphQLUrlGenerator urlGenerator) - : base(app, assetRepository, contentQuery, user) + : base(context, assetRepository, contentQuery) { CommandBus = commandBus; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs index 6456fd85a..dbf71ee12 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs @@ -5,14 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Security.Claims; using System.Threading.Tasks; -using Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public interface IGraphQLService { - Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query); + Task<(object Data, object[] Errors)> QueryAsync(QueryContext context, GraphQLQuery query); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index 205e3652c..7f2c2c4b1 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -7,21 +7,19 @@ using System; using System.Collections.Generic; -using System.Security.Claims; using System.Threading.Tasks; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentQueryService { - Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids); + Task> QueryAsync(QueryContext context, HashSet ids); - Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); + Task> QueryAsync(QueryContext context, string query); - Task FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any); + Task FindContentAsync(QueryContext context, Guid id, long version = EtagVersion.Any); - Task ThrowIfSchemaNotExistsAsync(IAppEntity app, string schemaIdOrName); + Task ThrowIfSchemaNotExistsAsync(QueryContext context); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs index daf4709f0..2c1c0ceea 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs @@ -1,145 +1,83 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; -using System.Threading.Tasks; 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.Reflection; +using Squidex.Infrastructure.Security; namespace Squidex.Domain.Apps.Entities.Contents { - public class QueryContext + public sealed class QueryContext : Cloneable { - private readonly ConcurrentDictionary cachedContents = new ConcurrentDictionary(); - private readonly ConcurrentDictionary cachedAssets = new ConcurrentDictionary(); - 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)); + public ClaimsPrincipal User { get; private set; } - this.assetRepository = assetRepository; - this.contentQuery = contentQuery; + public IAppEntity App { get; private set; } - this.user = user; + public IEnumerable Languages { get; private set; } - this.app = app; - } + public string SchemaIdOrName { get; private set; } - public async Task FindAssetAsync(Guid id) - { - var asset = cachedAssets.GetOrDefault(id); - - if (asset == null) - { - asset = await assetRepository.FindAssetAsync(id); + public bool Archived { get; private set; } - if (asset != null) - { - cachedAssets[asset.Id] = asset; - } - } + public bool Flatten { get; private set; } - return asset; + private QueryContext() + { } - public async Task FindContentAsync(Guid schemaId, Guid id) + public static QueryContext Create(IAppEntity app, ClaimsPrincipal user, IEnumerable 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(); - 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> QueryAssetsAsync(string query) + public QueryContext WithArchived(bool archived) { - var assets = await assetRepository.QueryAsync(app.Id, query); - - foreach (var asset in assets) - { - cachedAssets[asset.Id] = asset; - } - - return assets; + return Clone(c => c.Archived = archived); } - public async Task> QueryContentsAsync(string schemaIdOrName, string query) + public QueryContext WithFlatten(bool flatten) { - var result = await contentQuery.QueryAsync(app, schemaIdOrName, user, false, query); - - foreach (var content in result) - { - cachedContents[content.Id] = content; - } - - return result; + return Clone(c => c.Flatten = flatten); } - public async Task> GetReferencedAssetsAsync(ICollection ids) + public QueryContext WithSchemaName(string name) { - Guard.NotNull(ids, nameof(ids)); - - var notLoadedAssets = new HashSet(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(); + return Clone(c => c.SchemaIdOrName = name); } - public async Task> GetReferencedContentsAsync(Guid schemaId, ICollection ids) + public QueryContext WithSchemaId(Guid id) { - Guard.NotNull(ids, nameof(ids)); - - var notLoadedContents = new HashSet(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 Clone(c => c.SchemaIdOrName = id.ToString()); + } - return ids.Select(cachedContents.GetOrDefault).Where(x => x != null).ToList(); + public bool IsFrontendClient + { + get { return User.IsInClient("squidex-frontend"); } } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs new file mode 100644 index 000000000..9d29cbb8e --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs @@ -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 cachedContents = new ConcurrentDictionary(); + private readonly ConcurrentDictionary cachedAssets = new ConcurrentDictionary(); + 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 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 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> 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> 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> GetReferencedAssetsAsync(ICollection ids) + { + Guard.NotNull(ids, nameof(ids)); + + var notLoadedAssets = new HashSet(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> GetReferencedContentsAsync(Guid schemaId, ICollection ids) + { + Guard.NotNull(ids, nameof(ids)); + + var notLoadedContents = new HashSet(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(); + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index f5abee611..657bcc45b 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -61,7 +61,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(2)] public async Task PostGraphQL(string app, [FromBody] GraphQLQuery query) { - var result = await graphQl.QueryAsync(App, User, query); + var result = await graphQl.QueryAsync(Context(), query); if (result.Errors?.Length > 0) { @@ -108,17 +108,17 @@ namespace Squidex.Areas.Api.Controllers.Contents } } - var isFrontendClient = User.IsFrontendClient(); + var context = Context().WithSchemaName(name).WithArchived(archived); var result = idsList?.Count > 0 ? - await contentQuery.QueryAsync(App, name, User, archived, idsList) : - await contentQuery.QueryAsync(App, name, User, archived, Request.QueryString.ToString()); + await contentQuery.QueryAsync(context, idsList) : + await contentQuery.QueryAsync(context, Request.QueryString.ToString()); var response = new ContentsDto { Total = result.Total, - Items = result.Take(200).Select(ContentDto.FromContent).ToArray() + Items = result.Take(200).Select(x => ContentDto.FromContent(x, context)).ToArray() }; Response.Headers["Surrogate-Key"] = string.Join(" ", response.Items.Select(x => x.Id)); @@ -145,9 +145,10 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContent(string app, string name, Guid id) { - var content = await contentQuery.FindContentAsync(App, name, User, id); + var context = Context().WithSchemaName(name); + var content = await contentQuery.FindContentAsync(context, id); - var response = ContentDto.FromContent(content); + var response = ContentDto.FromContent(content, context); Response.Headers["ETag"] = content.Version.ToString(); Response.Headers["Surrogate-Key"] = content.Id.ToString(); @@ -176,9 +177,10 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContentVersion(string app, string name, Guid id, int version) { - var content = await contentQuery.FindContentAsync(App, name, User, id, version); + var context = Context().WithSchemaName(name); + var content = await contentQuery.FindContentAsync(context, id, version); - var response = ContentDto.FromContent(content); + var response = ContentDto.FromContent(content, context); Response.Headers["ETag"] = content.Version.ToString(); Response.Headers["Surrogate-Key"] = content.Id.ToString(); @@ -207,7 +209,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish }; @@ -241,7 +243,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PutContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = new UpdateContent { ContentId = id, Data = request.ToCleaned(), AsDraft = asDraft }; var context = await CommandBus.PublishAsync(command); @@ -274,7 +276,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = new PatchContent { ContentId = id, Data = request.ToCleaned(), AsDraft = asDraft }; var context = await CommandBus.PublishAsync(command); @@ -306,7 +308,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PublishContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = CreateCommand(id, Status.Published, dueTime); @@ -336,7 +338,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task UnpublishContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = CreateCommand(id, Status.Draft, dueTime); @@ -366,7 +368,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task ArchiveContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = CreateCommand(id, Status.Archived, dueTime); @@ -396,7 +398,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task RestoreContent(string app, string name, Guid id, string dueTime = null) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = CreateCommand(id, Status.Draft, dueTime); @@ -425,7 +427,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DiscardChanges(string app, string name, Guid id) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = new DiscardChanges { ContentId = id }; @@ -453,7 +455,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DeleteContent(string app, string name, Guid id) { - await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); + await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name)); var command = new DeleteContent { ContentId = id }; @@ -478,5 +480,10 @@ namespace Squidex.Areas.Api.Controllers.Contents return new ChangeContentStatus { Status = status, ContentId = id, DueTime = dt }; } + + private QueryContext Context() + { + return QueryContext.Create(App, User, Request.Headers["X-Languages"]).WithFlatten(Request.Headers.ContainsKey("X-Flatten")); + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 48885031d..0d3efa9a7 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -9,6 +9,7 @@ using System; using System.ComponentModel.DataAnnotations; using NodaTime; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; @@ -96,12 +97,20 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models return response; } - public static ContentDto FromContent(IContentEntity content) + public static ContentDto FromContent(IContentEntity content, QueryContext context) { var response = SimpleMapper.Map(content, new ContentDto()); - response.Data = content.Data; - response.DataDraft = content.DataDraft; + if (context.Flatten) + { + response.Data = content.Data?.ToFlatten(); + response.DataDraft = content.DataDraft?.ToFlatten(); + } + else + { + response.Data = content.Data; + response.DataDraft = content.DataDraft; + } if (content.ScheduleJob != null) { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs new file mode 100644 index 000000000..eaab86267 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs @@ -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 + { + { "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)data.ToFlatLanguageModel(fallbackConfig, new List { Language.DE }); + + var expected = new Dictionary + { + { "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)data.ToFlatLanguageModel(languagesConfig, new List { Language.DE, Language.EN }); + + var expected = new Dictionary + { + { "field1", 1 }, + { "field2", 4 }, + { "field3", 6 } + }; + + Assert.True(expected.EqualsDictionary(output)); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index 6b0ff52ab..ffd21cdc0 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -5,23 +5,16 @@ // 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 ContentConversionTests { private readonly Schema schema; - private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); public ContentConversionTests() { @@ -43,382 +36,103 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent new NamedContentData() .AddField("field1", new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")) + .AddValue("en", "EN")) .AddField("field2", new ContentFieldData() - .AddValue("iv", 3)) + .AddValue("iv", 1)) .AddField("invalid", new ContentFieldData() - .AddValue("iv", 3)); + .AddValue("iv", 2)); - var actual = input.ToIdModel(schema, false); + var actual = input.ToIdModel(schema, (data, field) => field.Name == "field2" ? null : data); var expected = new IdContentData() .AddField(1, new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")) - .AddField(2, - new ContentFieldData() - .AddValue("iv", 3)); + .AddValue("en", "EN")); Assert.Equal(expected, actual); } [Fact] - public void Should_convert_to_encoded_id_model() - { - var input = - new NamedContentData() - .AddField("json", - new ContentFieldData() - .AddValue("en", new JObject()) - .AddValue("de", null) - .AddValue("it", JValue.CreateNull())); - - var actual = input.ToIdModel(schema, true); - - var expected = - new IdContentData() - .AddField(4, - new ContentFieldData() - .AddValue("en", "e30=") - .AddValue("de", null) - .AddValue("it", null)); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Should_convert_to_name_model() + public void Should_convert_id_model() { var input = new IdContentData() .AddField(1, new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")) + .AddValue("en", "EN")) .AddField(2, new ContentFieldData() - .AddValue("iv", 3)) + .AddValue("iv", 1)) .AddField(99, new ContentFieldData() - .AddValue("iv", 3)); + .AddValue("iv", 2)); - var actual = input.ToNameModel(schema, false); + var actual = input.Convert(schema, (data, field) => field.Name == "field2" ? null : data); var expected = - new NamedContentData() - .AddField("field1", - new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")) - .AddField("field2", + new IdContentData() + .AddField(1, new ContentFieldData() - .AddValue("iv", 3)); + .AddValue("en", "EN")); Assert.Equal(expected, actual); } [Fact] - public void Should_convert_to_encoded_name_model() + public void Should_convert_to_name_model() { var input = new IdContentData() - .AddField(4, - new ContentFieldData() - .AddValue("en", "e30=") - .AddValue("de", null) - .AddValue("it", null)); - - var actual = input.ToNameModel(schema, true); - - Assert.True(actual["json"]["en"] is JObject); - } - - [Fact] - public void Should_cleanup_old_fields() - { - var input = - new NamedContentData() - .AddField("field0", - new ContentFieldData() - .AddValue("en", "en_string")) - .AddField("field1", + .AddField(1, new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")); - - var actual = input.ToApiModel(schema, languagesConfig); - - var expected = - new NamedContentData() - .AddField("field1", + .AddValue("en", "EN")) + .AddField(2, new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Should_cleanup_old_languages() - { - var input = - new NamedContentData() - .AddField("field1", + .AddValue("iv", 1)) + .AddField(99, new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string") - .AddValue("it", "it_string")); + .AddValue("iv", 2)); - var actual = input.ToApiModel(schema, languagesConfig); + var actual = input.ToNameModel(schema, (data, field) => field.Name == "field2" ? null : data); var expected = new NamedContentData() .AddField("field1", new ContentFieldData() - .AddValue("en", "en_string") - .AddValue("de", "de_string")); + .AddValue("en", "EN")); Assert.Equal(expected, actual); } [Fact] - public void Should_provide_invariant_from_master_language() + public void Should_convert_name_model() { var input = - new NamedContentData() - .AddField("field2", - new ContentFieldData() - .AddValue("de", 2) - .AddValue("en", 3)); - - 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 input = - new NamedContentData() - .AddField("field1", - new ContentFieldData() - .AddValue("iv", 3)); - - var actual = input.ToApiModel(schema, languagesConfig); - - var expected = - new NamedContentData() + new NamedContentData() .AddField("field1", new ContentFieldData() - .AddValue("en", 3)); - - Assert.Equal(expected, actual); - } - - [Fact] - 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 NamedContentData() + .AddValue("en", "EN")) .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 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 input = - new NamedContentData() - .AddField("field2", - new ContentFieldData() - .AddValue("de", 2) - .AddValue("it", 3)); - - 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 input = - new NamedContentData() - .AddField("field2", - new ContentFieldData() - .AddValue("iv", 5)) - .AddField("field3", + .AddValue("iv", 1)) + .AddField("invalid", new ContentFieldData() .AddValue("iv", 2)); - var actual = input.ToApiModel(schema, languagesConfig); + var actual = input.Convert(schema, (data, field) => field.Name == "field2" ? null : data); var expected = - new NamedContentData() - .AddField("field2", - new ContentFieldData() - .AddValue("iv", 5)); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Should_not_include_invalid_field_types() - { - var input = new NamedContentData() .AddField("field1", new ContentFieldData() - .AddValue("iv", "INVALID")) - .AddField("field2", - new ContentFieldData() - .AddValue("iv", 2)); - - var actual = input.ToApiModel(schema, languagesConfig, checkTypeCompatibility: true); - - var expected = - new NamedContentData() - .AddField("field2", - new ContentFieldData() - .AddValue("iv", 2)); + .AddValue("en", "EN")); Assert.Equal(expected, actual); } - [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.ToLanguageModel(languagesConfig)); - } - - [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)data.ToLanguageModel(fallbackConfig, new List { Language.DE }); - - var expected = new Dictionary - { - { "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)data.ToLanguageModel(languagesConfig, new List { Language.DE, Language.EN }); - - var expected = new Dictionary - { - { "field1", 1 }, - { "field2", 4 }, - { "field3", 6 } - }; - - Assert.True(expected.EqualsDictionary(output)); - } - [Fact] public void Should_be_equal_fields_when_they_have_same_value() { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs new file mode 100644 index 000000000..bbdf26965 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs @@ -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())(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); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index fde2d505e..4df64854a 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -11,6 +11,7 @@ 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.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; @@ -68,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds new ContentFieldData() .AddValue("iv", new JArray(id1.ToString(), id2.ToString()))); - var actual = input.ToCleanedReferences(schema, new HashSet(new[] { id2 })); + var actual = input.Convert(schema, FieldReferencesConverter.CleanReferences(new[] { id2 })); var cleanedValue = (JArray)actual[5]["iv"]; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index 55cc96e75..96bfef62c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -47,6 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private readonly ClaimsPrincipal user; private readonly ClaimsIdentity identity = new ClaimsIdentity(); private readonly EdmModelBuilder modelBuilder = A.Fake(); + private readonly QueryContext context; private readonly ContentQueryService sut; public ContentQueryServiceTests() @@ -64,6 +65,8 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => schema.SchemaDef).Returns(new Schema("my-schema")); + context = QueryContext.Create(app, user); + sut = new ContentQueryService(contentRepository, contentVersionLoader, appProvider, scriptEngine, modelBuilder); } @@ -73,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); - var result = await sut.GetSchemaAsync(app, schemaId.ToString()); + var result = await sut.GetSchemaAsync(context.WithSchemaId(schemaId)); Assert.Equal(schema, result); } @@ -84,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) .Returns(schema); - var result = await sut.GetSchemaAsync(app, "my-schema"); + var result = await sut.GetSchemaAsync(context.WithSchemaName("my-schema")); Assert.Equal(schema, result); } @@ -95,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) .Returns((ISchemaEntity)null); - await Assert.ThrowsAsync(() => sut.GetSchemaAsync(app, "my-schema")); + await Assert.ThrowsAsync(() => sut.GetSchemaAsync(context.WithSchemaName("my-schema"))); } [Fact] @@ -104,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) .Returns((ISchemaEntity)null); - await Assert.ThrowsAsync(() => sut.ThrowIfSchemaNotExistsAsync(app, "my-schema")); + await Assert.ThrowsAsync(() => sut.ThrowIfSchemaNotExistsAsync(context.WithSchemaName("my-schema"))); } public static IEnumerable SingleRequestData = new[] @@ -130,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "")) .Returns(contentTransformed); - var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId); + var result = await sut.FindContentAsync(context.WithSchemaId(schemaId), contentId); Assert.Equal(contentTransformed, result.Data); Assert.Equal(content.Id, result.Id); @@ -150,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "")) .Returns(contentTransformed); - var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId, 10); + var result = await sut.FindContentAsync(context.WithSchemaId(schemaId), contentId, 10); Assert.Equal(contentTransformed, result.Data); Assert.Equal(content.Id, result.Id); @@ -165,7 +168,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentRepository.FindContentAsync(app, schema, new[] { Status.Published }, contentId)) .Returns((IContentEntity)null); - await Assert.ThrowsAsync(async () => await sut.FindContentAsync(app, schemaId.ToString(), user, contentId)); + await Assert.ThrowsAsync(async () => await sut.FindContentAsync(context.WithSchemaId(schemaId), contentId)); } public static IEnumerable ManyRequestData = new[] @@ -189,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), A.Ignored)) .Returns(ResultList.Create(Enumerable.Repeat(content, count), total)); - var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, string.Empty); + var result = await sut.QueryAsync(context.WithSchemaId(schemaId).WithArchived(archive), string.Empty); Assert.Equal(contentData, result[0].Data); Assert.Equal(content.Id, result[0].Id); @@ -217,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => modelBuilder.BuildEdmModel(schema, app)) .Throws(new ODataException()); - return Assert.ThrowsAsync(() => sut.QueryAsync(app, schemaId.ToString(), user, false, "query")); + return Assert.ThrowsAsync(() => sut.QueryAsync(context.WithSchemaId(schemaId), "query")); } public static IEnumerable ManyIdRequestData = new[] @@ -243,7 +246,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), ids)) .Returns(ResultList.Create(Enumerable.Repeat(content, count), total)); - var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, ids); + var result = await sut.QueryAsync(context.WithSchemaId(schemaId).WithArchived(archive), ids); Assert.Equal(contentData, result[0].Data); Assert.Equal(content.Id, result[0].Id); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index aeebd190d..e0866ee5e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL new JObject( new JProperty("data", inputContent)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query, Variables = variables }); var expected = new { @@ -158,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL new JObject( new JProperty("data", inputContent)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query, Variables = variables }); var expected = new { @@ -247,7 +247,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL new JObject( new JProperty("data", inputContent)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query, Variables = variables }); var expected = new { @@ -310,7 +310,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(new EntitySavedResult(13)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -345,7 +345,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(new EntitySavedResult(13)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -380,7 +380,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(new EntitySavedResult(13)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -415,7 +415,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(new EntitySavedResult(13)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -450,7 +450,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL commandContext.Complete(new EntitySavedResult(13)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index 1c17347ed..8b75b5cf5 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [InlineData(" ")] public async Task Should_return_empty_object_for_empty_query(string query) { - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => assetRepository.QueryAsync(app.Id, "?$take=30&$skip=5&$search=my-query")) .Returns(ResultList.Create(assets, 0)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -137,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => assetRepository.QueryAsync(app.Id, "?$take=30&$skip=5&$search=my-query")) .Returns(ResultList.Create(assets, 10)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -206,7 +206,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => assetRepository.FindAssetAsync(assetId)) .Returns(asset); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -281,10 +281,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var contents = new List { content }; - A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, "?$top=30&$skip=5")) + A.CallTo(() => contentQuery.QueryAsync(ContextMatch(), "?$top=30&$skip=5")) .Returns(ResultList.Create(contents, 0)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -399,10 +399,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var contents = new List { content }; - A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, "?$top=30&$skip=5")) + A.CallTo(() => contentQuery.QueryAsync(ContextMatch(), "?$top=30&$skip=5")) .Returns(ResultList.Create(contents, 10)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -517,10 +517,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }} }}"; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + A.CallTo(() => contentQuery.FindContentAsync(ContextMatch(), contentId, EtagVersion.Any)) .Returns(content); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -610,13 +610,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var refContents = new List { contentRef }; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + A.CallTo(() => contentQuery.FindContentAsync(ContextMatch(), contentId, EtagVersion.Any)) .Returns(content); - A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, A>.That.Matches(x => x.Contains(contentRefId)))) + A.CallTo(() => contentQuery.QueryAsync(ContextMatch(), A>.That.Matches(x => x.Contains(contentRefId)))) .Returns(ResultList.Create(refContents, 0)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -670,13 +670,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var refAssets = new List { assetRef }; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + A.CallTo(() => contentQuery.FindContentAsync(ContextMatch(), contentId, EtagVersion.Any)) .Returns(content); A.CallTo(() => assetRepository.QueryAsync(app.Id, A>.That.Matches(x => x.Contains(assetRefId)))) .Returns(ResultList.Create(refAssets, 0)); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -729,10 +729,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }} }}"; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) + A.CallTo(() => contentQuery.FindContentAsync(ContextMatch(), contentId, EtagVersion.Any)) .Returns(content); - var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); var expected = new { @@ -741,5 +741,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL AssertResult(expected, result, false); } + + private QueryContext ContextMatch() + { + return A.That.Matches(x => x.App == app && x.SchemaIdOrName == schema.Id.ToString() && x.User == user && !x.Archived); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index 18754535c..e682d704b 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -45,6 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL protected readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); protected readonly IAppProvider appProvider = A.Fake(); protected readonly IAppEntity app = A.Dummy(); + protected readonly QueryContext context; protected readonly ClaimsPrincipal user = new ClaimsPrincipal(); protected readonly IGraphQLService sut; @@ -77,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE)); + context = QueryContext.Create(app, user); + A.CallTo(() => schema.Id).Returns(schemaId); A.CallTo(() => schema.Name).Returns(schemaDef.Name); A.CallTo(() => schema.SchemaDef).Returns(schemaDef);