Browse Source

Queries improved.

pull/294/head
Sebastian 8 years ago
parent
commit
5d1b13e037
  1. 166
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  2. 74
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs
  3. 265
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  4. 27
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  5. 35
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/FieldReferencesConverter.cs
  6. 1
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  7. 9
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  8. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  9. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  10. 140
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  11. 9
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs
  12. 11
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  13. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs
  14. 10
      src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs
  15. 126
      src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs
  16. 136
      src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs
  17. 43
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  18. 11
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  19. 153
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs
  20. 342
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
  21. 325
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
  22. 3
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
  23. 23
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  24. 16
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs
  25. 39
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  26. 3
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

166
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -5,23 +5,17 @@
// All rights reserved. Licensed under the MIT license. // 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.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
public delegate ContentFieldData FieldConverter(ContentFieldData data, Field field);
public static class ContentConverter 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)); Guard.NotNull(schema, nameof(schema));
@ -34,36 +28,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
continue; continue;
} }
if (decodeJsonField && field is JsonField) var fieldData = Convert(fieldValue.Value, field, converters);
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value) if (fieldData != null)
{
if (partitionValue.Value.IsNull())
{ {
encodedValue[partitionValue.Key] = null; result[field.Name] = fieldData;
}
else
{
var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString()));
encodedValue[partitionValue.Key] = JToken.Parse(value);
}
}
result[field.Name] = encodedValue;
}
else
{
result[field.Name] = fieldValue.Value;
} }
} }
return result; 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)); Guard.NotNull(schema, nameof(schema));
@ -76,153 +52,81 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
continue; continue;
} }
var fieldId = field.Id; var fieldData = Convert(fieldValue.Value, field, converters);
if (encodeJsonField && field is JsonField)
{
var encodedValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value)
{
if (partitionValue.Value.IsNull())
{
encodedValue[partitionValue.Key] = null;
}
else
{
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString()));
encodedValue[partitionValue.Key] = value;
}
}
result[fieldId] = encodedValue; if (fieldData != null)
}
else
{ {
result[fieldId] = fieldValue.Value; result[field.Id] = fieldData;
} }
} }
return result; 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(schema, nameof(schema));
Guard.NotNull(languagesConfig, nameof(languagesConfig));
var codeForInvariant = InvariantPartitioning.Instance.Master.Key; var result = new IdContentData();
var codeForMasterLanguage = languagesConfig.Master.Language.Iso2Code;
var result = new NamedContentData();
foreach (var fieldValue in content) 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; continue;
} }
if (checkTypeCompatibility) var fieldData = Convert(fieldValue.Value, field, converters);
{
var isValid = true;
foreach (var value in fieldValue.Value.Values) if (fieldData != null)
{
try
{
if (!value.IsNull())
{
JsonValueConverter.ConvertValue(field, value);
}
}
catch
{ {
isValid = false; result[field.Id] = fieldData;
break;
} }
} }
if (!isValid) return result;
{
continue;
}
} }
var fieldResult = new ContentFieldData(); public static NamedContentData Convert(this NamedContentData content, Schema schema, params FieldConverter[] converters)
var fieldValues = fieldValue.Value;
if (field.Partitioning.Equals(Partitioning.Language))
{
foreach (var languageConfig in languagesConfig)
{ {
var languageCode = languageConfig.Key; Guard.NotNull(schema, nameof(schema));
if (fieldValues.TryGetValue(languageCode, out var value)) var result = new NamedContentData();
{
fieldResult.Add(languageCode, value); foreach (var fieldValue in content)
}
else if (languageConfig == languagesConfig.Master && fieldValues.TryGetValue(codeForInvariant, out value))
{
fieldResult.Add(languageCode, value);
}
}
}
else
{
if (fieldValues.TryGetValue(codeForInvariant, out var value))
{ {
fieldResult.Add(codeForInvariant, value); if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out var field))
}
else if (fieldValues.TryGetValue(codeForMasterLanguage, out value))
{ {
fieldResult.Add(codeForInvariant, value); continue;
} }
else if (fieldValues.Count > 0)
var fieldData = Convert(fieldValue.Value, field, converters);
if (fieldData != null)
{ {
fieldResult.Add(codeForInvariant, fieldValues.Values.First()); result[field.Name] = fieldData;
} }
} }
result.Add(field.Name, fieldResult);
}
return result; return result;
} }
public static object ToLanguageModel(this NamedContentData content, LanguagesConfig languagesConfig, IReadOnlyCollection<Language> languagePreferences = null) private static ContentFieldData Convert(ContentFieldData fieldData, Field field, FieldConverter[] converters)
{ {
Guard.NotNull(languagesConfig, nameof(languagesConfig)); if (converters != null)
if (languagePreferences == null || languagePreferences.Count == 0)
{ {
return content; foreach (var converter in converters)
}
if (languagePreferences.Count == 1 && languagesConfig.TryGetConfig(languagePreferences.First(), out var languageConfig))
{ {
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList(); fieldData = converter(fieldData, field);
}
var result = new Dictionary<string, JToken>();
foreach (var fieldValue in content)
{
var fieldValues = fieldValue.Value;
foreach (var language in languagePreferences) if (fieldData == null)
{
if (fieldValues.TryGetValue(language, out var value) && value != null)
{ {
result[fieldValue.Key] = value;
break; break;
} }
} }
} }
return result; return fieldData;
} }
} }
} }

74
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<Language> languagePreferences = null)
{
Guard.NotNull(languagesConfig, nameof(languagesConfig));
if (languagePreferences == null || languagePreferences.Count == 0)
{
return content;
}
if (languagePreferences.Count == 1 && languagesConfig.TryGetConfig(languagePreferences.First(), out var languageConfig))
{
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList();
}
var result = new Dictionary<string, JToken>();
foreach (var fieldValue in content)
{
var fieldData = fieldValue.Value;
foreach (var language in languagePreferences)
{
if (fieldData.TryGetValue(language, out var value) && value != null)
{
result[fieldValue.Key] = value;
break;
}
}
}
return result;
}
public static Dictionary<string, object> ToFlatten(this NamedContentData content)
{
var result = new Dictionary<string, object>();
foreach (var fieldValue in content)
{
var fieldData = fieldValue.Value;
if (fieldData.Count == 1)
{
result[fieldValue.Key] = fieldData.Values.First();
}
else
{
result[fieldValue.Key] = fieldData;
}
}
return result;
}
}
}

265
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<Language> languages)
{
if (languages == null)
{
return (data, field) => data;
}
var languageCodes =
new HashSet<string>(
languages.Select(x => x.Iso2Code).Where(x => languagesConfig.Contains(x)),
StringComparer.OrdinalIgnoreCase);
if (languageCodes.Count == 0)
{
return (data, field) => data;
}
return (data, field) =>
{
if (field.Partitioning.Equals(Partitioning.Language))
{
var result = new ContentFieldData();
foreach (var languageCode in languageCodes)
{
if (data.TryGetValue(languageCode, out var value))
{
result[languageCode] = value;
}
}
return result;
}
return data;
};
}
public static FieldConverter DecodeJson()
{
return (data, field) =>
{
if (field is JsonField)
{
var result = new ContentFieldData();
foreach (var partitionValue in data)
{
if (partitionValue.Value.IsNull())
{
result[partitionValue.Key] = null;
}
else
{
var value = Encoding.UTF8.GetString(Convert.FromBase64String(partitionValue.Value.ToString()));
result[partitionValue.Key] = JToken.Parse(value);
}
}
return result;
}
return data;
};
}
public static FieldConverter EncodeJson()
{
return (data, field) =>
{
if (field is JsonField)
{
var result = new ContentFieldData();
foreach (var partitionValue in data)
{
if (partitionValue.Value.IsNull())
{
result[partitionValue.Key] = null;
}
else
{
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(partitionValue.Value.ToString()));
result[partitionValue.Key] = value;
}
}
return result;
}
return data;
};
}
}
}

27
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 class ContentReferencesExtensions
{ {
public static IdContentData ToCleanedReferences(this IdContentData source, Schema schema, ISet<Guid> 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<Guid> GetReferencedIds(this IdContentData source, Schema schema) public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, Schema schema)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));

35
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<Guid> deletedReferencedIds)
{
var ids = new HashSet<Guid>(deletedReferencedIds);
return (data, field) =>
{
foreach (var partitionValue in data.Where(x => !x.Value.IsNull()).ToList())
{
var newValue = field.CleanReferences(partitionValue.Value, ids);
data[partitionValue.Key] = newValue;
}
return data;
};
}
}
}

1
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -17,6 +17,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public static class ReferencesCleaner public static class ReferencesCleaner
{ {
private static readonly List<Guid> EmptyIds = new List<Guid>(); private static readonly List<Guid> EmptyIds = new List<Guid>();
public static JToken CleanReferences(this Field field, JToken value, ISet<Guid> oldReferences) public static JToken CleanReferences(this Field field, JToken value, ISet<Guid> oldReferences)
{ {
if ((field is AssetsField || field is ReferencesField) && !value.IsNull()) if ((field is AssetsField || field is ReferencesField) && !value.IsNull())

9
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(); return data.GetReferencedIds(schema).ToList();
} }
public static NamedContentData ToData(this IdContentData idData, Schema schema, List<Guid> deletedIds) public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds)
{ {
return idData.ToCleanedReferences(schema, new HashSet<Guid>(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<T>(this ContentData<T> data) public static string ToFullText<T>(this ContentData<T> data)

4
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) public void ParseData(Schema schema)
{ {
data = DataByIds.ToData(schema, ReferencedIdsDeleted); data = DataByIds.FromMongoModel(schema, ReferencedIdsDeleted);
if (DataDraftByIds != null) if (DataDraftByIds != null)
{ {
dataDraft = DataDraftByIds.ToData(schema, ReferencedIdsDeleted); dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted);
} }
} }
} }

5
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; 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 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 var content = SimpleMapper.Map(value, new MongoContentEntity
{ {
DataByIds = idData, DataByIds = idData,
DataDraftByIds = value.DataDraft?.ToIdModel(schema.SchemaDef, true), DataDraftByIds = value.DataDraft?.ToMongoModel(schema.SchemaDef),
IsDeleted = value.IsDeleted, IsDeleted = value.IsDeleted,
IndexedAppId = value.AppId.Id, IndexedAppId = value.AppId.Id,
IndexedSchemaId = value.SchemaId.Id, IndexedSchemaId = value.SchemaId.Id,

140
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -8,21 +8,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.OData; using Microsoft.OData;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Scripting; 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.Edm;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
@ -58,108 +55,90 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.modelBuilder = modelBuilder; 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<IContentEntity> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = -1) public async Task<IContentEntity> FindContentAsync(QueryContext context, Guid id, long version = -1)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(context, nameof(context));
Guard.NotNull(user, nameof(user));
Guard.NotEmpty(id, nameof(id)); Guard.NotEmpty(id, nameof(id));
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName));
var schema = await GetSchemaAsync(app, schemaIdOrName); var schema = await GetSchemaAsync(context);
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var isVersioned = version > EtagVersion.Empty; var isVersioned = version > EtagVersion.Empty;
var isFrontend = IsFrontendClient(user);
var parsedStatus = isFrontend ? StatusAll : StatusPublished; var parsedStatus = context.IsFrontendClient ? StatusAll : StatusPublished;
var content = var content =
isVersioned ? isVersioned ?
await FindContentByVersionAsync(id, version) : 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)); throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity));
} }
return TransformContent(app, schema, user, content, isFrontend, isVersioned); return TransformContent(context, schema, true, content);
} }
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) public async Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string query)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(context, nameof(context));
Guard.NotNull(user, nameof(user));
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName));
var schema = await GetSchemaAsync(app, schemaIdOrName); var schema = await GetSchemaAsync(context);
using (Profiler.TraceMethod<ContentQueryService>("QueryAsyncByQuery")) using (Profiler.TraceMethod<ContentQueryService>("QueryAsyncByQuery"))
{ {
var isFrontend = IsFrontendClient(user); var parsedQuery = ParseQuery(context, query, schema);
var parsedStatus = ParseStatus(context);
var parsedQuery = ParseQuery(app, query, schema); var contents = await contentRepository.QueryAsync(context.App, schema, parsedStatus, parsedQuery);
var parsedStatus = ParseStatus(isFrontend, archived);
var contents = await contentRepository.QueryAsync(app, schema, parsedStatus, parsedQuery); return TransformContents(context, schema, true, contents);
return TransformContents(app, schema, user, contents, false, isFrontend);
} }
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids) public async Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, HashSet<Guid> ids)
{ {
Guard.NotNull(context, nameof(context));
Guard.NotNull(ids, nameof(ids)); 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<ContentQueryService>("QueryAsyncByIds")) using (Profiler.TraceMethod<ContentQueryService>("QueryAsyncByIds"))
{ {
var isFrontend = IsFrontendClient(user); var parsedStatus = ParseStatus(context);
var parsedStatus = ParseStatus(isFrontend, archived);
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, private IContentEntity TransformContent(QueryContext context, ISchemaEntity schema, bool checkType, IContentEntity content)
IContentEntity content,
bool isFrontend,
bool isVersioned)
{ {
return TransformContents(app, schema, user, Enumerable.Repeat(content, 1), isVersioned, isFrontend).FirstOrDefault(); return TransformContents(context, schema, checkType, Enumerable.Repeat(content, 1)).FirstOrDefault();
} }
private IResultList<IContentEntity> TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, private IResultList<IContentEntity> TransformContents(QueryContext context, ISchemaEntity schema, bool checkType, IResultList<IContentEntity> contents)
IResultList<IContentEntity> contents,
bool isTypeChecking,
bool isFrontendClient)
{ {
var transformed = TransformContents(app, schema, user, (IEnumerable<IContentEntity>)contents, isTypeChecking, isFrontendClient); var transformed = TransformContents(context, schema, checkType, (IEnumerable<IContentEntity>)contents);
return ResultList.Create(transformed, contents.Total); return ResultList.Create(transformed, contents.Total);
} }
private IEnumerable<IContentEntity> TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, private IEnumerable<IContentEntity> TransformContents(QueryContext context, ISchemaEntity schema, bool checkType, IEnumerable<IContentEntity> contents)
IEnumerable<IContentEntity> contents,
bool isTypeChecking,
bool isFrontendClient)
{ {
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var converters = GenerateConverters(context, checkType).ToArray();
var scriptText = schema.ScriptQuery; var scriptText = schema.ScriptQuery;
var isScripting = !string.IsNullOrWhiteSpace(scriptText); var isScripting = !string.IsNullOrWhiteSpace(scriptText);
@ -170,17 +149,17 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (result.Data != null) 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) 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; yield return result;
@ -188,13 +167,35 @@ namespace Squidex.Domain.Apps.Entities.Contents
} }
} }
private ODataUriParser ParseQuery(IAppEntity app, string query, ISchemaEntity schema) private IEnumerable<FieldConverter> 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<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
try try
{ {
var model = modelBuilder.BuildEdmModel(schema, app); var model = modelBuilder.BuildEdmModel(schema, context.App);
return model.ParseQuery(query); return model.ParseQuery(query);
} }
@ -205,35 +206,33 @@ namespace Squidex.Domain.Apps.Entities.Contents
} }
} }
public async Task<ISchemaEntity> GetSchemaAsync(IAppEntity app, string schemaIdOrName) public async Task<ISchemaEntity> GetSchemaAsync(QueryContext context)
{ {
Guard.NotNull(app, nameof(app));
ISchemaEntity schema = null; 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) if (schema == null)
{ {
schema = await appProvider.GetSchemaAsync(app.Id, schemaIdOrName); schema = await appProvider.GetSchemaAsync(context.App.Id, context.SchemaIdOrName);
} }
if (schema == null) if (schema == null)
{ {
throw new DomainObjectNotFoundException(schemaIdOrName, typeof(ISchemaEntity)); throw new DomainObjectNotFoundException(context.SchemaIdOrName, typeof(ISchemaEntity));
} }
return schema; 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; return StatusArchived;
} }
@ -249,14 +248,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
return contentVersionLoader.LoadAsync(id, version); return contentVersionLoader.LoadAsync(id, version);
} }
private Task<IContentEntity> FindContentAsync(IAppEntity app, Guid id, Status[] status, ISchemaEntity schema) private Task<IContentEntity> FindContentAsync(QueryContext context, Guid id, Status[] status, ISchemaEntity schema)
{
return contentRepository.FindContentAsync(app, schema, status, id);
}
private static bool IsFrontendClient(ClaimsPrincipal user)
{ {
return user.IsInClient("squidex-frontend"); return contentRepository.FindContentAsync(context.App, schema, status, id);
} }
} }
} }

9
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
@ -47,9 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
this.urlGenerator = urlGenerator; 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)); Guard.NotNull(query, nameof(query));
if (string.IsNullOrWhiteSpace(query.Query)) if (string.IsNullOrWhiteSpace(query.Query))
@ -57,9 +56,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return (new object(), new object[0]); 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); return await modelContext.ExecuteAsync(ctx, query);
} }

11
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -7,25 +7,26 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public sealed class GraphQLExecutionContext : QueryContext public sealed class GraphQLExecutionContext : QueryExecutionContext
{ {
public ICommandBus CommandBus { get; } public ICommandBus CommandBus { get; }
public IGraphQLUrlGenerator UrlGenerator { 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) IGraphQLUrlGenerator urlGenerator)
: base(app, assetRepository, contentQuery, user) : base(context, assetRepository, contentQuery)
{ {
CommandBus = commandBus; CommandBus = commandBus;

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs

@ -5,14 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public interface IGraphQLService 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);
} }
} }

10
src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs

@ -7,21 +7,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentQueryService public interface IContentQueryService
{ {
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids); Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, HashSet<Guid> ids);
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string query);
Task<IContentEntity> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any); Task<IContentEntity> FindContentAsync(QueryContext context, Guid id, long version = EtagVersion.Any);
Task ThrowIfSchemaNotExistsAsync(IAppEntity app, string schemaIdOrName); Task ThrowIfSchemaNotExistsAsync(QueryContext context);
} }
} }

126
src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs

@ -1,145 +1,83 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public class QueryContext public sealed class QueryContext : Cloneable<QueryContext>
{ {
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>(); public ClaimsPrincipal User { get; private set; }
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>();
private readonly IContentQueryService contentQuery;
private readonly IAssetRepository assetRepository;
private readonly IAppEntity app;
private readonly ClaimsPrincipal user;
public QueryContext(
IAppEntity app,
IAssetRepository assetRepository,
IContentQueryService contentQuery,
ClaimsPrincipal user)
{
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(app, nameof(app));
Guard.NotNull(user, nameof(user));
this.assetRepository = assetRepository; public IAppEntity App { get; private set; }
this.contentQuery = contentQuery;
this.user = user; public IEnumerable<Language> Languages { get; private set; }
this.app = app; public string SchemaIdOrName { get; private set; }
}
public async Task<IAssetEntity> FindAssetAsync(Guid id) public bool Archived { get; private set; }
{
var asset = cachedAssets.GetOrDefault(id);
if (asset == null) public bool Flatten { get; private set; }
{
asset = await assetRepository.FindAssetAsync(id);
if (asset != null) private QueryContext()
{ {
cachedAssets[asset.Id] = asset;
} }
}
return asset;
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id)
{
var content = cachedContents.GetOrDefault(id);
if (content == null) public static QueryContext Create(IAppEntity app, ClaimsPrincipal user, IEnumerable<string> languageCodes = null)
{ {
content = await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id); var result = new QueryContext { App = app, User = user };
if (content != null) if (languageCodes != null)
{ {
cachedContents[content.Id] = content; var languages = new List<Language>();
}
}
return content;
}
public async Task<IResultList<IAssetEntity>> QueryAssetsAsync(string query) foreach (var iso2Code in languageCodes)
{ {
var assets = await assetRepository.QueryAsync(app.Id, query); if (Language.TryGetLanguage(iso2Code, out var language))
foreach (var asset in assets)
{ {
cachedAssets[asset.Id] = asset; languages.Add(language);
} }
return assets;
} }
public async Task<IResultList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query) result.Languages = languages;
{
var result = await contentQuery.QueryAsync(app, schemaIdOrName, user, false, query);
foreach (var content in result)
{
cachedContents[content.Id] = content;
} }
return result; return result;
} }
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids) public QueryContext WithArchived(bool archived)
{
Guard.NotNull(ids, nameof(ids));
var notLoadedAssets = new HashSet<Guid>(ids.Where(id => !cachedAssets.ContainsKey(id)));
if (notLoadedAssets.Count > 0)
{
var assets = await assetRepository.QueryAsync(app.Id, notLoadedAssets);
foreach (var asset in assets)
{ {
cachedAssets[asset.Id] = asset; return Clone(c => c.Archived = archived);
}
} }
return ids.Select(cachedAssets.GetOrDefault).Where(x => x != null).ToList(); public QueryContext WithFlatten(bool flatten)
}
public async Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, ICollection<Guid> ids)
{ {
Guard.NotNull(ids, nameof(ids)); return Clone(c => c.Flatten = flatten);
}
var notLoadedContents = new HashSet<Guid>(ids.Where(id => !cachedContents.ContainsKey(id)));
if (notLoadedContents.Count > 0) public QueryContext WithSchemaName(string name)
{ {
var result = await contentQuery.QueryAsync(app, schemaId.ToString(), user, false, notLoadedContents); return Clone(c => c.SchemaIdOrName = name);
}
foreach (var content in result) public QueryContext WithSchemaId(Guid id)
{ {
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"); }
} }
} }
} }

136
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<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>();
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>();
private readonly IContentQueryService contentQuery;
private readonly IAssetRepository assetRepository;
private readonly QueryContext context;
public QueryExecutionContext(QueryContext context,
IAssetRepository assetRepository,
IContentQueryService contentQuery)
{
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(context, nameof(context));
this.assetRepository = assetRepository;
this.contentQuery = contentQuery;
this.context = context;
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
{
var asset = cachedAssets.GetOrDefault(id);
if (asset == null)
{
asset = await assetRepository.FindAssetAsync(id);
if (asset != null)
{
cachedAssets[asset.Id] = asset;
}
}
return asset;
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id)
{
var content = cachedContents.GetOrDefault(id);
if (content == null)
{
content = await contentQuery.FindContentAsync(context.WithSchemaId(schemaId), id);
if (content != null)
{
cachedContents[content.Id] = content;
}
}
return content;
}
public async Task<IResultList<IAssetEntity>> QueryAssetsAsync(string query)
{
var assets = await assetRepository.QueryAsync(context.App.Id, query);
foreach (var asset in assets)
{
cachedAssets[asset.Id] = asset;
}
return assets;
}
public async Task<IResultList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query)
{
var result = await contentQuery.QueryAsync(context.WithSchemaName(schemaIdOrName), query);
foreach (var content in result)
{
cachedContents[content.Id] = content;
}
return result;
}
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids)
{
Guard.NotNull(ids, nameof(ids));
var notLoadedAssets = new HashSet<Guid>(ids.Where(id => !cachedAssets.ContainsKey(id)));
if (notLoadedAssets.Count > 0)
{
var assets = await assetRepository.QueryAsync(context.App.Id, notLoadedAssets);
foreach (var asset in assets)
{
cachedAssets[asset.Id] = asset;
}
}
return ids.Select(cachedAssets.GetOrDefault).Where(x => x != null).ToList();
}
public async Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, ICollection<Guid> ids)
{
Guard.NotNull(ids, nameof(ids));
var notLoadedContents = new HashSet<Guid>(ids.Where(id => !cachedContents.ContainsKey(id)));
if (notLoadedContents.Count > 0)
{
var result = await contentQuery.QueryAsync(context.WithSchemaId(schemaId), notLoadedContents);
foreach (var content in result)
{
cachedContents[content.Id] = content;
}
}
return ids.Select(cachedContents.GetOrDefault).Where(x => x != null).ToList();
}
}
}

43
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -61,7 +61,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(2)] [ApiCosts(2)]
public async Task<IActionResult> PostGraphQL(string app, [FromBody] GraphQLQuery query) public async Task<IActionResult> 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) 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 = var result =
idsList?.Count > 0 ? idsList?.Count > 0 ?
await contentQuery.QueryAsync(App, name, User, archived, idsList) : await contentQuery.QueryAsync(context, idsList) :
await contentQuery.QueryAsync(App, name, User, archived, Request.QueryString.ToString()); await contentQuery.QueryAsync(context, Request.QueryString.ToString());
var response = new ContentsDto var response = new ContentsDto
{ {
Total = result.Total, 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)); Response.Headers["Surrogate-Key"] = string.Join(" ", response.Items.Select(x => x.Id));
@ -145,9 +145,10 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> GetContent(string app, string name, Guid id) public async Task<IActionResult> 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["ETag"] = content.Version.ToString();
Response.Headers["Surrogate-Key"] = content.Id.ToString(); Response.Headers["Surrogate-Key"] = content.Id.ToString();
@ -176,9 +177,10 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> GetContentVersion(string app, string name, Guid id, int version) public async Task<IActionResult> 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["ETag"] = content.Version.ToString();
Response.Headers["Surrogate-Key"] = content.Id.ToString(); Response.Headers["Surrogate-Key"] = content.Id.ToString();
@ -207,7 +209,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) public async Task<IActionResult> 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 }; var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish };
@ -241,7 +243,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PutContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) public async Task<IActionResult> 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 command = new UpdateContent { ContentId = id, Data = request.ToCleaned(), AsDraft = asDraft };
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);
@ -274,7 +276,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) public async Task<IActionResult> 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 command = new PatchContent { ContentId = id, Data = request.ToCleaned(), AsDraft = asDraft };
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);
@ -306,7 +308,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> PublishContent(string app, string name, Guid id, string dueTime = null) public async Task<IActionResult> 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); var command = CreateCommand(id, Status.Published, dueTime);
@ -336,7 +338,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> UnpublishContent(string app, string name, Guid id, string dueTime = null) public async Task<IActionResult> 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); var command = CreateCommand(id, Status.Draft, dueTime);
@ -366,7 +368,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> ArchiveContent(string app, string name, Guid id, string dueTime = null) public async Task<IActionResult> 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); var command = CreateCommand(id, Status.Archived, dueTime);
@ -396,7 +398,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> RestoreContent(string app, string name, Guid id, string dueTime = null) public async Task<IActionResult> 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); var command = CreateCommand(id, Status.Draft, dueTime);
@ -425,7 +427,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> DiscardChanges(string app, string name, Guid id) public async Task<IActionResult> DiscardChanges(string app, string name, Guid id)
{ {
await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name));
var command = new DiscardChanges { ContentId = id }; var command = new DiscardChanges { ContentId = id };
@ -453,7 +455,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> DeleteContent(string app, string name, Guid id) public async Task<IActionResult> DeleteContent(string app, string name, Guid id)
{ {
await contentQuery.ThrowIfSchemaNotExistsAsync(App, name); await contentQuery.ThrowIfSchemaNotExistsAsync(Context().WithSchemaName(name));
var command = new DeleteContent { ContentId = id }; 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 }; 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"));
}
} }
} }

11
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -9,6 +9,7 @@ using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -96,12 +97,20 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
return response; return response;
} }
public static ContentDto FromContent(IContentEntity content) public static ContentDto FromContent(IContentEntity content, QueryContext context)
{ {
var response = SimpleMapper.Map(content, new ContentDto()); var response = SimpleMapper.Map(content, new ContentDto());
if (context.Flatten)
{
response.Data = content.Data?.ToFlatten();
response.DataDraft = content.DataDraft?.ToFlatten();
}
else
{
response.Data = content.Data; response.Data = content.Data;
response.DataDraft = content.DataDraft; response.DataDraft = content.DataDraft;
}
if (content.ScheduleJob != null) if (content.ScheduleJob != null)
{ {

153
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<string, object>
{
{ "field1", new ContentFieldData().AddValue("de", 1).AddValue("en", 2) },
{ "field2", new ContentFieldData().AddValue("de", null).AddValue("en", 4) },
{ "field3", (JValue)6 },
{ "field4", (JValue)7 }
};
Assert.True(expected.EqualsDictionary(output));
}
[Fact]
public void Should_return_flat_list_when_single_languages_specified()
{
var data =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("de", 1)
.AddValue("en", 2))
.AddField("field2",
new ContentFieldData()
.AddValue("de", null)
.AddValue("en", 4))
.AddField("field3",
new ContentFieldData()
.AddValue("en", 6))
.AddField("field4",
new ContentFieldData()
.AddValue("it", 7));
var fallbackConfig =
LanguagesConfig.Build(
new LanguageConfig(Language.EN),
new LanguageConfig(Language.DE, false, Language.EN));
var output = (Dictionary<string, JToken>)data.ToFlatLanguageModel(fallbackConfig, new List<Language> { Language.DE });
var expected = new Dictionary<string, JToken>
{
{ "field1", 1 },
{ "field2", 4 },
{ "field3", 6 }
};
Assert.True(expected.EqualsDictionary(output));
}
[Fact]
public void Should_return_flat_list_when_languages_specified()
{
var data =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("de", 1)
.AddValue("en", 2))
.AddField("field2",
new ContentFieldData()
.AddValue("de", null)
.AddValue("en", 4))
.AddField("field3",
new ContentFieldData()
.AddValue("en", 6))
.AddField("field4",
new ContentFieldData()
.AddValue("it", 7));
var output = (Dictionary<string, JToken>)data.ToFlatLanguageModel(languagesConfig, new List<Language> { Language.DE, Language.EN });
var expected = new Dictionary<string, JToken>
{
{ "field1", 1 },
{ "field2", 4 },
{ "field3", 6 }
};
Assert.True(expected.EqualsDictionary(output));
}
}
}

342
tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs

@ -5,23 +5,16 @@
// All rights reserved. Licensed under the MIT license. // 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.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Xunit; using Xunit;
#pragma warning disable xUnit2013 // Do not use equality check to check for collection size.
namespace Squidex.Domain.Apps.Core.Operations.ConvertContent namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{ {
public class ContentConversionTests public class ContentConversionTests
{ {
private readonly Schema schema; private readonly Schema schema;
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE);
public ContentConversionTests() public ContentConversionTests()
{ {
@ -43,380 +36,101 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
new NamedContentData() new NamedContentData()
.AddField("field1", .AddField("field1",
new ContentFieldData() new ContentFieldData()
.AddValue("en", "en_string") .AddValue("en", "EN"))
.AddValue("de", "de_string"))
.AddField("field2", .AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 3)) .AddValue("iv", 1))
.AddField("invalid", .AddField("invalid",
new ContentFieldData() 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 = var expected =
new IdContentData() new IdContentData()
.AddField(1, .AddField(1,
new ContentFieldData() new ContentFieldData()
.AddValue("en", "en_string") .AddValue("en", "EN"));
.AddValue("de", "de_string"))
.AddField(2,
new ContentFieldData()
.AddValue("iv", 3));
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_convert_to_encoded_id_model() public void Should_convert_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()
{ {
var input = var input =
new IdContentData() new IdContentData()
.AddField(1, .AddField(1,
new ContentFieldData() new ContentFieldData()
.AddValue("en", "en_string") .AddValue("en", "EN"))
.AddValue("de", "de_string"))
.AddField(2, .AddField(2,
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 3)) .AddValue("iv", 1))
.AddField(99, .AddField(99,
new ContentFieldData() 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 = var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"))
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 3));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_convert_to_encoded_name_model()
{
var input =
new IdContentData() new IdContentData()
.AddField(4, .AddField(1,
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",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"));
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_cleanup_old_languages()
{
var input =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string")
.AddValue("it", "it_string"));
var actual = input.ToApiModel(schema, languagesConfig);
var expected =
new NamedContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", "en_string")
.AddValue("de", "de_string"));
Assert.Equal(expected, actual);
}
[Fact]
public void Should_provide_invariant_from_master_language()
{
var 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()
.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()
.AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("en", 2)); .AddValue("en", "EN"));
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_remove_null_values_from_id_model_when_cleaning() public void Should_convert_to_name_model()
{ {
var input = var input =
new IdContentData() new IdContentData()
.AddField(1, null) .AddField(1,
.AddField(2,
new ContentFieldData() new ContentFieldData()
.AddValue("en", 2) .AddValue("en", "EN"))
.AddValue("it", null));
var actual = input.ToCleaned();
var expected =
new IdContentData()
.AddField(2, .AddField(2,
new ContentFieldData() new ContentFieldData()
.AddValue("en", 2)); .AddValue("iv", 1))
.AddField(99,
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",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 2)); .AddValue("iv", 2));
var actual = input.ToApiModel(schema, languagesConfig); var actual = input.ToNameModel(schema, (data, field) => field.Name == "field2" ? null : data);
var expected = var expected =
new NamedContentData() new NamedContentData()
.AddField("field2", .AddField("field1",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 5)); .AddValue("en", "EN"));
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_not_include_invalid_field_types() public void Should_convert_name_model()
{ {
var input = var input =
new NamedContentData() new NamedContentData()
.AddField("field1", .AddField("field1",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", "INVALID")) .AddValue("en", "EN"))
.AddField("field2", .AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 2)); .AddValue("iv", 1))
.AddField("invalid",
var actual = input.ToApiModel(schema, languagesConfig, checkTypeCompatibility: true);
var expected =
new NamedContentData()
.AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 2)); .AddValue("iv", 2));
Assert.Equal(expected, actual); var actual = input.Convert(schema, (data, field) => field.Name == "field2" ? null : data);
}
[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<string, JToken>)data.ToLanguageModel(fallbackConfig, new List<Language> { Language.DE });
var expected = new Dictionary<string, JToken>
{
{ "field1", 1 },
{ "field2", 4 },
{ "field3", 6 }
};
Assert.True(expected.EqualsDictionary(output));
}
[Fact] var expected =
public void Should_return_flat_list_when_languages_specified()
{
var data =
new NamedContentData() new NamedContentData()
.AddField("field1", .AddField("field1",
new ContentFieldData() new ContentFieldData()
.AddValue("de", 1) .AddValue("en", "EN"));
.AddValue("en", 2))
.AddField("field2",
new ContentFieldData()
.AddValue("de", null)
.AddValue("en", 4))
.AddField("field3",
new ContentFieldData()
.AddValue("en", 6))
.AddField("field4",
new ContentFieldData()
.AddValue("it", 7));
var output = (Dictionary<string, JToken>)data.ToLanguageModel(languagesConfig, new List<Language> { Language.DE, Language.EN });
var expected = new Dictionary<string, JToken> Assert.Equal(expected, actual);
{
{ "field1", 1 },
{ "field2", 4 },
{ "field3", 6 }
};
Assert.True(expected.EqualsDictionary(output));
} }
[Fact] [Fact]

325
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<Language>())(source, stringLanguageField);
Assert.Same(source, result);
}
[Fact]
public void Should_return_same_values_if_filtering_languages_from_invariant_field()
{
var source = new ContentFieldData();
var result = FieldConverters.FilterLanguages(languagesConfig, null)(source, stringInvariantField);
Assert.Same(source, result);
}
}
}

3
tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs

@ -11,6 +11,7 @@ using System.Linq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -68,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds
new ContentFieldData() new ContentFieldData()
.AddValue("iv", new JArray(id1.ToString(), id2.ToString()))); .AddValue("iv", new JArray(id1.ToString(), id2.ToString())));
var actual = input.ToCleanedReferences(schema, new HashSet<Guid>(new[] { id2 })); var actual = input.Convert(schema, FieldReferencesConverter.CleanReferences(new[] { id2 }));
var cleanedValue = (JArray)actual[5]["iv"]; var cleanedValue = (JArray)actual[5]["iv"];

23
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 ClaimsPrincipal user;
private readonly ClaimsIdentity identity = new ClaimsIdentity(); private readonly ClaimsIdentity identity = new ClaimsIdentity();
private readonly EdmModelBuilder modelBuilder = A.Fake<EdmModelBuilder>(); private readonly EdmModelBuilder modelBuilder = A.Fake<EdmModelBuilder>();
private readonly QueryContext context;
private readonly ContentQueryService sut; private readonly ContentQueryService sut;
public ContentQueryServiceTests() public ContentQueryServiceTests()
@ -64,6 +65,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => schema.SchemaDef).Returns(new Schema("my-schema")); A.CallTo(() => schema.SchemaDef).Returns(new Schema("my-schema"));
context = QueryContext.Create(app, user);
sut = new ContentQueryService(contentRepository, contentVersionLoader, appProvider, scriptEngine, modelBuilder); 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)) A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false))
.Returns(schema); .Returns(schema);
var result = await sut.GetSchemaAsync(app, schemaId.ToString()); var result = await sut.GetSchemaAsync(context.WithSchemaId(schemaId));
Assert.Equal(schema, result); Assert.Equal(schema, result);
} }
@ -84,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema"))
.Returns(schema); .Returns(schema);
var result = await sut.GetSchemaAsync(app, "my-schema"); var result = await sut.GetSchemaAsync(context.WithSchemaName("my-schema"));
Assert.Equal(schema, result); Assert.Equal(schema, result);
} }
@ -95,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema"))
.Returns((ISchemaEntity)null); .Returns((ISchemaEntity)null);
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaAsync(app, "my-schema")); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaAsync(context.WithSchemaName("my-schema")));
} }
[Fact] [Fact]
@ -104,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema"))
.Returns((ISchemaEntity)null); .Returns((ISchemaEntity)null);
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.ThrowIfSchemaNotExistsAsync(app, "my-schema")); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.ThrowIfSchemaNotExistsAsync(context.WithSchemaName("my-schema")));
} }
public static IEnumerable<object[]> SingleRequestData = new[] public static IEnumerable<object[]> SingleRequestData = new[]
@ -130,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "<query-script>")) A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "<query-script>"))
.Returns(contentTransformed); .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(contentTransformed, result.Data);
Assert.Equal(content.Id, result.Id); Assert.Equal(content.Id, result.Id);
@ -150,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "<query-script>")) A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "<query-script>"))
.Returns(contentTransformed); .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(contentTransformed, result.Data);
Assert.Equal(content.Id, result.Id); 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)) A.CallTo(() => contentRepository.FindContentAsync(app, schema, new[] { Status.Published }, contentId))
.Returns((IContentEntity)null); .Returns((IContentEntity)null);
await Assert.ThrowsAsync<DomainObjectNotFoundException>(async () => await sut.FindContentAsync(app, schemaId.ToString(), user, contentId)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(async () => await sut.FindContentAsync(context.WithSchemaId(schemaId), contentId));
} }
public static IEnumerable<object[]> ManyRequestData = new[] public static IEnumerable<object[]> ManyRequestData = new[]
@ -189,7 +192,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), A<ODataUriParser>.Ignored)) A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), A<ODataUriParser>.Ignored))
.Returns(ResultList.Create(Enumerable.Repeat(content, count), total)); .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(contentData, result[0].Data);
Assert.Equal(content.Id, result[0].Id); Assert.Equal(content.Id, result[0].Id);
@ -217,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => modelBuilder.BuildEdmModel(schema, app)) A.CallTo(() => modelBuilder.BuildEdmModel(schema, app))
.Throws(new ODataException()); .Throws(new ODataException());
return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(app, schemaId.ToString(), user, false, "query")); return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context.WithSchemaId(schemaId), "query"));
} }
public static IEnumerable<object[]> ManyIdRequestData = new[] public static IEnumerable<object[]> ManyIdRequestData = new[]
@ -243,7 +246,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), ids)) A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), ids))
.Returns(ResultList.Create(Enumerable.Repeat(content, count), total)); .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(contentData, result[0].Data);
Assert.Equal(content.Id, result[0].Id); Assert.Equal(content.Id, result[0].Id);

16
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs

@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new JObject( new JObject(
new JProperty("data", inputContent)); 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 var expected = new
{ {
@ -158,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new JObject( new JObject(
new JProperty("data", inputContent)); 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 var expected = new
{ {
@ -247,7 +247,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new JObject( new JObject(
new JProperty("data", inputContent)); 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 var expected = new
{ {
@ -310,7 +310,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
commandContext.Complete(new EntitySavedResult(13)); 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 var expected = new
{ {
@ -345,7 +345,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
commandContext.Complete(new EntitySavedResult(13)); 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 var expected = new
{ {
@ -380,7 +380,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
commandContext.Complete(new EntitySavedResult(13)); 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 var expected = new
{ {
@ -415,7 +415,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
commandContext.Complete(new EntitySavedResult(13)); 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 var expected = new
{ {
@ -450,7 +450,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
commandContext.Complete(new EntitySavedResult(13)); 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 var expected = new
{ {

39
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
[InlineData(" ")] [InlineData(" ")]
public async Task Should_return_empty_object_for_empty_query(string query) 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 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")) A.CallTo(() => assetRepository.QueryAsync(app.Id, "?$take=30&$skip=5&$search=my-query"))
.Returns(ResultList.Create(assets, 0)); .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 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")) A.CallTo(() => assetRepository.QueryAsync(app.Id, "?$take=30&$skip=5&$search=my-query"))
.Returns(ResultList.Create(assets, 10)); .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 var expected = new
{ {
@ -206,7 +206,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => assetRepository.FindAssetAsync(assetId)) A.CallTo(() => assetRepository.FindAssetAsync(assetId))
.Returns(asset); .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 var expected = new
{ {
@ -281,10 +281,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var contents = new List<IContentEntity> { content }; var contents = new List<IContentEntity> { 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)); .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 var expected = new
{ {
@ -399,10 +399,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var contents = new List<IContentEntity> { content }; var contents = new List<IContentEntity> { 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)); .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 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); .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 var expected = new
{ {
@ -610,13 +610,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var refContents = new List<IContentEntity> { contentRef }; var refContents = new List<IContentEntity> { contentRef };
A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) A.CallTo(() => contentQuery.FindContentAsync(ContextMatch(), contentId, EtagVersion.Any))
.Returns(content); .Returns(content);
A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, A<HashSet<Guid>>.That.Matches(x => x.Contains(contentRefId)))) A.CallTo(() => contentQuery.QueryAsync(ContextMatch(), A<HashSet<Guid>>.That.Matches(x => x.Contains(contentRefId))))
.Returns(ResultList.Create(refContents, 0)); .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 var expected = new
{ {
@ -670,13 +670,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var refAssets = new List<IAssetEntity> { assetRef }; var refAssets = new List<IAssetEntity> { assetRef };
A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) A.CallTo(() => contentQuery.FindContentAsync(ContextMatch(), contentId, EtagVersion.Any))
.Returns(content); .Returns(content);
A.CallTo(() => assetRepository.QueryAsync(app.Id, A<HashSet<Guid>>.That.Matches(x => x.Contains(assetRefId)))) A.CallTo(() => assetRepository.QueryAsync(app.Id, A<HashSet<Guid>>.That.Matches(x => x.Contains(assetRefId))))
.Returns(ResultList.Create(refAssets, 0)); .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 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); .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 var expected = new
{ {
@ -741,5 +741,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
AssertResult(expected, result, false); AssertResult(expected, result, false);
} }
private QueryContext ContextMatch()
{
return A<QueryContext>.That.Matches(x => x.App == app && x.SchemaIdOrName == schema.Id.ToString() && x.User == user && !x.Archived);
}
} }
} }

3
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 IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
protected readonly IAppProvider appProvider = A.Fake<IAppProvider>(); protected readonly IAppProvider appProvider = A.Fake<IAppProvider>();
protected readonly IAppEntity app = A.Dummy<IAppEntity>(); protected readonly IAppEntity app = A.Dummy<IAppEntity>();
protected readonly QueryContext context;
protected readonly ClaimsPrincipal user = new ClaimsPrincipal(); protected readonly ClaimsPrincipal user = new ClaimsPrincipal();
protected readonly IGraphQLService sut; protected readonly IGraphQLService sut;
@ -77,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.Name).Returns(appName);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE)); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE));
context = QueryContext.Create(app, user);
A.CallTo(() => schema.Id).Returns(schemaId); A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.Name).Returns(schemaDef.Name); A.CallTo(() => schema.Name).Returns(schemaDef.Name);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef); A.CallTo(() => schema.SchemaDef).Returns(schemaDef);

Loading…
Cancel
Save