Browse Source

Enrichment for status color.

pull/377/head
Sebastian Stehle 7 years ago
parent
commit
d7477239fc
  1. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  2. 53
      src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  3. 4
      src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs
  4. 40
      src/Squidex.Domain.Apps.Entities/Assets/AssetEnricher.cs
  5. 2
      src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs
  6. 15
      src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs
  7. 4
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEntityEnriched.cs
  9. 6
      src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs
  10. 41
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  11. 60
      src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs
  12. 8
      src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs
  13. 144
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  14. 10
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  15. 1
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs
  16. 8
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs
  17. 8
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs
  18. 4
      src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs
  19. 6
      src/Squidex.Domain.Apps.Entities/Contents/IContentEntityEnriched.cs
  20. 6
      src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs
  21. 6
      src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs
  22. 10
      src/Squidex.Infrastructure/CollectionExtensions.cs
  23. 2
      src/Squidex.Infrastructure/ResultList.cs
  24. 2
      src/Squidex.Web/UrlHelperExtensions.cs
  25. 2
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  26. 2
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  27. 2
      src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs
  28. 12
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  29. 21
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  30. 20
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  31. 13
      src/Squidex/Config/Domain/EntitiesServices.cs
  32. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs
  33. 84
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs
  34. 68
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs
  35. 10
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs
  36. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs
  37. 70
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
  38. 85
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs
  39. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  40. 18
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs
  41. 32
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs
  42. 32
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  43. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  44. 7
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
if (fullTextIds?.Count == 0)
{
return ResultList.Create<IContentEntity>(0);
return ResultList.CreateFrom<IContentEntity>(0);
}
return await contents.QueryAsync(schema, query, fullTextIds, status, inDraft, includeDraft);

53
src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -66,33 +66,29 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var existings = await assetQuery.QueryByHashAsync(createAsset.AppId.Id, createAsset.FileHash);
AssetCreatedResult result = null;
foreach (var existing in existings)
{
if (IsDuplicate(createAsset, existing))
{
result = new AssetCreatedResult(existing, true);
}
var result = new AssetCreatedResult(existing, true);
break;
context.Complete(result);
return;
}
}
if (result == null)
foreach (var tagGenerator in tagGenerators)
{
foreach (var tagGenerator in tagGenerators)
{
tagGenerator.GenerateTags(createAsset, createAsset.Tags);
}
tagGenerator.GenerateTags(createAsset, createAsset.Tags);
}
var asset = (IEnrichedAssetEntity)await ExecuteAndAdjustTagsAsync(createAsset);
await HandleCoreAsync(context, next);
result = new AssetCreatedResult(asset, false);
var asset = context.PlainResult as IAssetEntityEnriched;
await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null);
}
context.Complete(new AssetCreatedResult(asset, false));
context.Complete(result);
await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null);
}
finally
{
@ -109,11 +105,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
try
{
var result = (IEnrichedAssetEntity)await ExecuteAndAdjustTagsAsync(updateAsset);
await HandleCoreAsync(context, next);
context.Complete(result);
var asset = context.PlainResult as IAssetEntity;
await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.FileVersion, null);
await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), asset.FileVersion, null);
}
finally
{
@ -123,34 +119,23 @@ namespace Squidex.Domain.Apps.Entities.Assets
break;
}
case AssetCommand command:
{
var result = await ExecuteAndAdjustTagsAsync(command);
context.Complete(result);
break;
}
default:
await base.HandleAsync(context, next);
await HandleCoreAsync(context, next);
break;
}
}
private async Task<object> ExecuteAndAdjustTagsAsync(AssetCommand command)
private async Task HandleCoreAsync(CommandContext context, Func<Task> next)
{
var result = await ExecuteCommandAsync(command);
await HandleAsync(context, next);
if (result is IAssetEntity asset)
if (context.PlainResult is IAssetEntity asset && !(context.PlainResult is IAssetEntityEnriched))
{
var enriched = await assetEnricher.EnrichAsync(asset);
return enriched;
context.Complete(enriched);
}
return result;
}
private static bool IsDuplicate(CreateAsset createAsset, IAssetEntity asset)

4
src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs

@ -9,11 +9,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetCreatedResult
{
public IEnrichedAssetEntity Asset { get; }
public IAssetEntityEnriched Asset { get; }
public bool IsDuplicate { get; }
public AssetCreatedResult(IEnrichedAssetEntity asset, bool isDuplicate)
public AssetCreatedResult(IAssetEntityEnriched asset, bool isDuplicate)
{
Asset = asset;

40
src/Squidex.Domain.Apps.Entities/Assets/AssetEnricher.cs

@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets
@ -25,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
this.tagService = tagService;
}
public async Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset)
public async Task<IAssetEntityEnriched> EnrichAsync(IAssetEntity asset)
{
Guard.NotNull(asset, nameof(asset));
@ -34,38 +35,41 @@ namespace Squidex.Domain.Apps.Entities.Assets
return enriched[0];
}
public async Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets)
public async Task<IReadOnlyList<IAssetEntityEnriched>> EnrichAsync(IEnumerable<IAssetEntity> assets)
{
Guard.NotNull(assets, nameof(assets));
var results = new List<IEnrichedAssetEntity>();
foreach (var group in assets.GroupBy(x => x.AppId.Id))
using (Profiler.TraceMethod<AssetEnricher>())
{
var tagsById = await CalculateTags(group);
var results = new List<IAssetEntityEnriched>();
foreach (var asset in group)
foreach (var group in assets.GroupBy(x => x.AppId.Id))
{
var result = SimpleMapper.Map(asset, new AssetEntity());
result.TagNames = new HashSet<string>();
var tagsById = await CalculateTags(group);
if (asset.Tags != null)
foreach (var asset in group)
{
foreach (var id in asset.Tags)
var result = SimpleMapper.Map(asset, new AssetEntity());
result.TagNames = new HashSet<string>();
if (asset.Tags != null)
{
if (tagsById.TryGetValue(id, out var name))
foreach (var id in asset.Tags)
{
result.TagNames.Add(name);
if (tagsById.TryGetValue(id, out var name))
{
result.TagNames.Add(name);
}
}
}
}
results.Add(result);
results.Add(result);
}
}
}
return results;
return results;
}
}
private async Task<Dictionary<string, string>> CalculateTags(IGrouping<System.Guid, IAssetEntity> group)

2
src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetEntity : IEnrichedAssetEntity
public sealed class AssetEntity : IAssetEntityEnriched
{
public NamedId<Guid> AppId { get; set; }

15
src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.OData;
@ -50,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
this.options = options.Value;
}
public async Task<IEnrichedAssetEntity> FindAssetAsync( Guid id)
public async Task<IAssetEntityEnriched> FindAssetAsync( Guid id)
{
var asset = await assetRepository.FindAssetAsync(id);
@ -62,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
return null;
}
public async Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Guid appId, string hash)
public async Task<IReadOnlyList<IAssetEntityEnriched>> QueryByHashAsync(Guid appId, string hash)
{
Guard.NotNull(hash, nameof(hash));
@ -71,14 +70,14 @@ namespace Squidex.Domain.Apps.Entities.Assets
return await assetEnricher.EnrichAsync(assets);
}
public async Task<IResultList<IEnrichedAssetEntity>> QueryAsync(QueryContext context, Q query)
public async Task<IResultList<IAssetEntityEnriched>> QueryAsync(QueryContext context, Q query)
{
Guard.NotNull(context, nameof(context));
Guard.NotNull(query, nameof(query));
IResultList<IAssetEntity> assets;
if (query.Ids != null)
if (query.Ids != null && query.Ids.Count > 0)
{
assets = await QueryByIdsAsync(context, query);
}
@ -89,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var enriched = await assetEnricher.EnrichAsync(assets);
return ResultList.Create<IEnrichedAssetEntity>(assets.Total, enriched);
return ResultList.Create(assets.Total, enriched);
}
private async Task<IResultList<IAssetEntity>> QueryByQueryAsync(QueryContext context, Q query)
@ -108,9 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
private static IResultList<IAssetEntity> Sort(IResultList<IAssetEntity> assets, IReadOnlyList<Guid> ids)
{
var sorted = ids.Select(id => assets.FirstOrDefault(x => x.Id == id)).Where(x => x != null);
return ResultList.Create(assets.Total, sorted);
return assets.SortSet(x => x.Id, ids);
}
private Query ParseQuery(QueryContext context, string query)

4
src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs

@ -12,8 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
public interface IAssetEnricher
{
Task<IEnrichedAssetEntity> EnrichAsync(IAssetEntity asset);
Task<IAssetEntityEnriched> EnrichAsync(IAssetEntity asset);
Task<IReadOnlyList<IEnrichedAssetEntity>> EnrichAsync(IEnumerable<IAssetEntity> assets);
Task<IReadOnlyList<IAssetEntityEnriched>> EnrichAsync(IEnumerable<IAssetEntity> assets);
}
}

2
src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs → src/Squidex.Domain.Apps.Entities/Assets/IAssetEntityEnriched.cs

@ -9,7 +9,7 @@ using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Assets
{
public interface IEnrichedAssetEntity : IAssetEntity
public interface IAssetEntityEnriched : IAssetEntity
{
HashSet<string> TagNames { get; }
}

6
src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs

@ -16,10 +16,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
int DefaultPageSizeGraphQl { get; }
Task<IReadOnlyList<IEnrichedAssetEntity>> QueryByHashAsync(Guid appId, string hash);
Task<IReadOnlyList<IAssetEntityEnriched>> QueryByHashAsync(Guid appId, string hash);
Task<IResultList<IEnrichedAssetEntity>> QueryAsync(QueryContext contex, Q query);
Task<IResultList<IAssetEntityEnriched>> QueryAsync(QueryContext contex, Q query);
Task<IEnrichedAssetEntity> FindAssetAsync(Guid id);
Task<IAssetEntityEnriched> FindAssetAsync(Guid id);
}
}

41
src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentCommandMiddleware : GrainCommandMiddleware<ContentCommand, IContentGrain>
{
private readonly IContentEnricher contentEnricher;
public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher)
: base(grainFactory)
{
Guard.NotNull(contentEnricher, nameof(contentEnricher));
this.contentEnricher = contentEnricher;
}
public override async Task HandleAsync(CommandContext context, Func<Task> next)
{
await HandleAsync(context, next);
if (context.PlainResult is IContentEntity content)
{
var enriched = await contentEnricher.EnrichAsync(content);
context.Complete(enriched);
}
}
}
}

60
src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection;
@ -15,6 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentEnricher : IContentEnricher
{
private const string DefaultColor = StatusColors.Draft;
private readonly IContentWorkflow contentWorkflow;
public ContentEnricher(IContentWorkflow contentWorkflow)
@ -22,33 +26,65 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.contentWorkflow = contentWorkflow;
}
public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents)
public async Task<IContentEntityEnriched> EnrichAsync(IContentEntity content)
{
var results = new List<ContentEntity>();
Guard.NotNull(content, nameof(content));
var enriched = await EnrichAsync(Enumerable.Repeat(content, 1));
return enriched[0];
}
public async Task<IReadOnlyList<IContentEntityEnriched>> EnrichAsync(IEnumerable<IContentEntity> contents)
{
Guard.NotNull(contents, nameof(contents));
using (Profiler.TraceMethod<ContentEnricher>())
{
var cache = new Dictionary<Status, StatusInfo>();
var results = new List<ContentEntity>();
var cache = new Dictionary<(Guid, Status), StatusInfo>();
foreach (var content in contents)
{
var result = SimpleMapper.Map(content, new ContentEntity());
if (!cache.TryGetValue(content.Status, out var info))
{
info = await contentWorkflow.GetInfoAsync(content.Status);
await ResolveColorAsync(content, result, cache);
await ResolveNextsAsync(content, result);
await ResolveCanUpdateAsync(content, result);
cache[content.Status] = info;
}
results.Add(result);
}
result.StatusInfo = info;
result.Nexts = await contentWorkflow.GetNextsAsync(content);
return results;
}
}
results.Add(result);
private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result)
{
result.CanUpdate = await contentWorkflow.CanUpdateAsync(content);
}
private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result)
{
result.Nexts = await contentWorkflow.GetNextsAsync(content);
}
private async Task ResolveColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache)
{
if (!cache.TryGetValue((content.SchemaId.Id, content.Status), out var info))
{
info = await contentWorkflow.GetInfoAsync(content.Status);
if (info == null)
{
info = new StatusInfo(content.Status, DefaultColor);
}
cache[(content.SchemaId.Id, content.Status)] = info;
}
return results;
result.StatusColor = info.Color;
}
}
}

8
src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentEntity : IEnrichedContentEntity
public sealed class ContentEntity : IContentEntityEnriched
{
public Guid Id { get; set; }
@ -38,10 +38,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
public Status Status { get; set; }
public StatusInfo StatusInfo { get; set; }
public StatusInfo[] Nexts { get; set; }
public string StatusColor { get; set; }
public bool CanUpdate { get; set; }
public bool IsPending { get; set; }
}
}

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

@ -34,10 +34,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentQueryService : IContentQueryService
{
private static readonly Status[] StatusPublishedOnly = { Status.Published };
private readonly IContentRepository contentRepository;
private readonly IContentVersionLoader contentVersionLoader;
private static readonly IResultList<IContentEntityEnriched> EmptyContents = ResultList.CreateFrom<IContentEntityEnriched>(0);
private readonly IAppProvider appProvider;
private readonly IAssetUrlGenerator assetUrlGenerator;
private readonly IContentEnricher contentEnricher;
private readonly IContentRepository contentRepository;
private readonly IContentVersionLoader contentVersionLoader;
private readonly IScriptEngine scriptEngine;
private readonly ContentOptions options;
private readonly EdmModelBuilder modelBuilder;
@ -50,6 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentQueryService(
IAppProvider appProvider,
IAssetUrlGenerator assetUrlGenerator,
IContentEnricher contentEnricher,
IContentRepository contentRepository,
IContentVersionLoader contentVersionLoader,
IScriptEngine scriptEngine,
@ -58,6 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(assetUrlGenerator, nameof(assetUrlGenerator));
Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(contentVersionLoader, nameof(contentVersionLoader));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
@ -66,6 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.appProvider = appProvider;
this.assetUrlGenerator = assetUrlGenerator;
this.contentEnricher = contentEnricher;
this.contentRepository = contentRepository;
this.contentVersionLoader = contentVersionLoader;
this.modelBuilder = modelBuilder;
@ -73,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.scriptEngine = scriptEngine;
}
public async Task<IContentEntity> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = -1)
public async Task<IContentEntityEnriched> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = -1)
{
Guard.NotNull(context, nameof(context));
@ -83,25 +88,27 @@ namespace Squidex.Domain.Apps.Entities.Contents
using (Profiler.TraceMethod<ContentQueryService>())
{
var isVersioned = version > EtagVersion.Empty;
var status = GetStatus(context);
IContentEntity content;
var content =
isVersioned ?
await FindContentByVersionAsync(id, version) :
await FindContentAsync(context, id, status, schema);
if (version > EtagVersion.Empty)
{
content = await FindByVersionAsync(id, version);
}
else
{
content = await FindCoreAsync(context, id, schema);
}
if (content == null || (content.Status != Status.Published && !context.IsFrontendClient) || content.SchemaId.Id != schema.Id)
{
throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity));
}
return Transform(context, schema, content);
return await TransformAsync(context, schema, content);
}
}
public async Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string schemaIdOrName, Q query)
public async Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, string schemaIdOrName, Q query)
{
Guard.NotNull(context, nameof(context));
@ -111,95 +118,88 @@ namespace Squidex.Domain.Apps.Entities.Contents
using (Profiler.TraceMethod<ContentQueryService>())
{
var status = GetStatus(context);
IResultList<IContentEntity> contents;
if (query.Ids?.Count > 0)
if (query.Ids != null && query.Ids.Count > 0)
{
contents = await QueryAsync(context, schema, query.Ids.ToHashSet(), status);
contents = SortSet(contents, query.Ids);
contents = await QueryByIdsAsync(context, query, schema);
}
else
{
var parsedQuery = ParseQuery(context, query.ODataQuery, schema);
contents = await QueryAsync(context, schema, parsedQuery, status);
contents = await QueryByQueryAsync(context, query, schema);
}
return Transform(context, schema, contents);
return await TransformAsync(context, schema, contents);
}
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids)
public async Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids)
{
Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<ContentQueryService>())
{
var status = GetStatus(context);
List<IContentEntity> result;
if (ids?.Count > 0)
if (ids == null || ids.Count == 0)
{
var contents = await QueryAsync(context, ids, status);
return EmptyContents;
}
var permissions = context.User.Permissions();
var results = new List<IContentEntityEnriched>();
contents = contents.Where(x => HasPermission(permissions, x.Schema)).ToList();
var contents = await QueryCoreAsync(context, ids);
result = contents.Select(x => Transform(context, x.Schema, x.Content)).ToList();
result = SortList(result, ids).ToList();
}
else
var permissions = context.User.Permissions();
foreach (var group in contents.GroupBy(x => x.Schema.Id))
{
result = new List<IContentEntity>();
var schema = group.First().Schema;
if (HasPermission(permissions, schema))
{
var enriched = await TransformCoreAsync(context, schema, group.Select(x => x.Content));
results.AddRange(enriched);
}
}
return result;
return ResultList.Create(results.Count, results.SortList(x => x.Id, ids));
}
}
private IResultList<IContentEntity> Transform(QueryContext context, ISchemaEntity schema, IResultList<IContentEntity> contents)
private async Task<IResultList<IContentEntityEnriched>> TransformAsync(QueryContext context, ISchemaEntity schema, IResultList<IContentEntity> contents)
{
var transformed = TransformCore(context, schema, contents);
var transformed = await TransformCoreAsync(context, schema, contents);
return ResultList.Create(contents.Total, transformed);
}
private IContentEntity Transform(QueryContext context, ISchemaEntity schema, IContentEntity content)
private async Task<IContentEntityEnriched> TransformAsync(QueryContext context, ISchemaEntity schema, IContentEntity content)
{
return TransformCore(context, schema, Enumerable.Repeat(content, 1)).FirstOrDefault();
}
var transformed = await TransformCoreAsync(context, schema, Enumerable.Repeat(content, 1));
private static IResultList<IContentEntity> SortSet(IResultList<IContentEntity> contents, IReadOnlyList<Guid> ids)
{
return ResultList.Create(contents.Total, SortList(contents, ids));
return transformed[0];
}
private static IEnumerable<IContentEntity> SortList(IEnumerable<IContentEntity> contents, IReadOnlyList<Guid> ids)
{
return ids.Select(id => contents.FirstOrDefault(x => x.Id == id)).Where(x => x != null);
}
private IEnumerable<IContentEntity> TransformCore(QueryContext context, ISchemaEntity schema, IEnumerable<IContentEntity> contents)
private async Task<IReadOnlyList<IContentEntityEnriched>> TransformCoreAsync(QueryContext context, ISchemaEntity schema, IEnumerable<IContentEntity> contents)
{
using (Profiler.TraceMethod<ContentQueryService>())
{
var results = new List<IContentEntityEnriched>();
var converters = GenerateConverters(context).ToArray();
var scriptText = schema.SchemaDef.Scripts.Query;
var scripting = !string.IsNullOrWhiteSpace(scriptText);
var isScripting = !string.IsNullOrWhiteSpace(scriptText);
var enriched = await contentEnricher.EnrichAsync(contents);
foreach (var content in contents)
foreach (var content in enriched)
{
var result = SimpleMapper.Map(content, new ContentEntity());
if (result.Data != null)
{
if (!context.IsFrontendClient && isScripting)
if (!context.IsFrontendClient && scripting)
{
var ctx = new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id };
@ -218,8 +218,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.DataDraft = null;
}
yield return result;
results.Add(result);
}
return results;
}
}
@ -344,32 +346,46 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
}
private Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids, Status[] status)
private async Task<IResultList<IContentEntity>> QueryByQueryAsync(QueryContext context, Q query, ISchemaEntity schema)
{
var parsedQuery = ParseQuery(context, query.ODataQuery, schema);
return await QueryCoreAsync(context, schema, parsedQuery);
}
private async Task<IResultList<IContentEntity>> QueryByIdsAsync(QueryContext context, Q query, ISchemaEntity schema)
{
var contents = await QueryCoreAsync(context, schema, query.Ids.ToHashSet());
return contents.SortSet(x => x.Id, query.Ids);
}
private Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryCoreAsync(QueryContext context, IReadOnlyList<Guid> ids)
{
return contentRepository.QueryAsync(context.App, status, new HashSet<Guid>(ids), ShouldIncludeDraft(context));
return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet<Guid>(ids), WithDraft(context));
}
private Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, ISchemaEntity schema, Query query, Status[] status)
private Task<IResultList<IContentEntity>> QueryCoreAsync(QueryContext context, ISchemaEntity schema, Query query)
{
return contentRepository.QueryAsync(context.App, schema, status, context.IsFrontendClient, query, ShouldIncludeDraft(context));
return contentRepository.QueryAsync(context.App, schema, GetStatus(context), context.IsFrontendClient, query, WithDraft(context));
}
private Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, ISchemaEntity schema, HashSet<Guid> ids, Status[] status)
private Task<IResultList<IContentEntity>> QueryCoreAsync(QueryContext context, ISchemaEntity schema, HashSet<Guid> ids)
{
return contentRepository.QueryAsync(context.App, schema, status, ids, ShouldIncludeDraft(context));
return contentRepository.QueryAsync(context.App, schema, GetStatus(context), ids, WithDraft(context));
}
private Task<IContentEntity> FindContentAsync(QueryContext context, Guid id, Status[] status, ISchemaEntity schema)
private Task<IContentEntity> FindCoreAsync(QueryContext context, Guid id, ISchemaEntity schema)
{
return contentRepository.FindContentAsync(context.App, schema, status, id, ShouldIncludeDraft(context));
return contentRepository.FindContentAsync(context.App, schema, GetStatus(context), id, WithDraft(context));
}
private Task<IContentEntity> FindContentByVersionAsync(Guid id, long version)
private Task<IContentEntity> FindByVersionAsync(Guid id, long version)
{
return contentVersionLoader.LoadAsync(id, version);
}
private static bool ShouldIncludeDraft(QueryContext context)
private static bool WithDraft(QueryContext context)
{
return context.ApiStatus == StatusForApi.All || context.IsFrontendClient;
}

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

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public sealed class GraphQLExecutionContext : QueryExecutionContext
{
private static readonly List<IEnrichedAssetEntity> EmptyAssets = new List<IEnrichedAssetEntity>();
private static readonly List<IAssetEntityEnriched> EmptyAssets = new List<IAssetEntityEnriched>();
private static readonly List<IContentEntity> EmptyContents = new List<IContentEntity>();
private readonly IDataLoaderContextAccessor dataLoaderContextAccessor;
private readonly IDependencyResolver resolver;
@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
execution.UserContext = this;
}
public override Task<IEnrichedAssetEntity> FindAssetAsync(Guid id)
public override Task<IAssetEntityEnriched> FindAssetAsync(Guid id)
{
var dataLoader = GetAssetsLoader();
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return dataLoader.LoadAsync(id);
}
public async Task<IReadOnlyList<IEnrichedAssetEntity>> GetReferencedAssetsAsync(IJsonValue value)
public async Task<IReadOnlyList<IAssetEntityEnriched>> GetReferencedAssetsAsync(IJsonValue value)
{
var ids = ParseIds(value);
@ -95,9 +95,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return await dataLoader.LoadManyAsync(ids);
}
private IDataLoader<Guid, IEnrichedAssetEntity> GetAssetsLoader()
private IDataLoader<Guid, IAssetEntityEnriched> GetAssetsLoader()
{
return dataLoaderContextAccessor.Context.GetOrAddBatchLoader<Guid, IEnrichedAssetEntity>("Assets",
return dataLoaderContextAccessor.Context.GetOrAddBatchLoader<Guid, IAssetEntityEnriched>("Assets",
async batch =>
{
var result = await GetReferencedAssetsAsync(new List<Guid>(batch));

1
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs

@ -7,7 +7,6 @@
using System;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types

8
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class AssetGraphType : ObjectGraphType<IEnrichedAssetEntity>
public sealed class AssetGraphType : ObjectGraphType<IAssetEntityEnriched>
{
public AssetGraphType(IGraphModel model)
{
@ -167,7 +167,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
Name = "tags",
ResolvedType = null,
Resolver = Resolve(x => x.Tags),
Resolver = Resolve(x => x.TagNames),
Description = "The asset tags.",
Type = AllTypes.NonNullTagsType
});
@ -186,9 +186,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = "An asset";
}
private static IFieldResolver Resolve(Func<IEnrichedAssetEntity, object> action)
private static IFieldResolver Resolve(Func<IAssetEntityEnriched, object> action)
{
return new FuncFieldResolver<IEnrichedAssetEntity, object>(c => action(c.Source));
return new FuncFieldResolver<IAssetEntityEnriched, object>(c => action(c.Source));
}
}
}

8
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs

@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class ContentGraphType : ObjectGraphType<IContentEntity>
public sealed class ContentGraphType : ObjectGraphType<IContentEntityEnriched>
{
public void Initialize(IGraphModel model, ISchemaEntity schema, IComplexGraphType contentDataType)
{
@ -78,7 +78,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = $"The the status of the {schemaName} content."
});
/*
AddField(new FieldType
{
Name = "statusColor",
@ -86,7 +85,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Resolver = Resolve(x => x.StatusColor),
Description = $"The color status of the {schemaName} content."
});
*/
AddField(new FieldType
{
@ -118,9 +116,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = $"The structure of a {schemaName} content type.";
}
private static IFieldResolver Resolve(Func<IContentEntity, object> action)
private static IFieldResolver Resolve(Func<IContentEntityEnriched, object> action)
{
return new FuncFieldResolver<IContentEntity, object>(c => action(c.Source));
return new FuncFieldResolver<IContentEntityEnriched, object>(c => action(c.Source));
}
}
}

4
src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs

@ -12,6 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IContentEnricher
{
Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents);
Task<IContentEntityEnriched> EnrichAsync(IContentEntity content);
Task<IReadOnlyList<IContentEntityEnriched>> EnrichAsync(IEnumerable<IContentEntity> contents);
}
}

6
src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs → src/Squidex.Domain.Apps.Entities/Contents/IContentEntityEnriched.cs

@ -9,9 +9,11 @@ using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents
{
public interface IEnrichedContentEntity : IContentEntity
public interface IContentEntityEnriched : IContentEntity
{
StatusInfo StatusInfo { get; }
bool CanUpdate { get; }
string StatusColor { get; }
StatusInfo[] Nexts { get; }
}

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

@ -17,11 +17,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
int DefaultPageSizeGraphQl { get; }
Task<IReadOnlyList<IContentEntity>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids);
Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids);
Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string schemaIdOrName, Q query);
Task<IResultList<IContentEntityEnriched>> QueryAsync(QueryContext context, string schemaIdOrName, Q query);
Task<IContentEntity> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any);
Task<IContentEntityEnriched> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any);
Task<ISchemaEntity> GetSchemaOrThrowAsync(QueryContext context, string schemaIdOrName);
}

6
src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public class QueryExecutionContext
{
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>();
private readonly ConcurrentDictionary<Guid, IEnrichedAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IEnrichedAssetEntity>();
private readonly ConcurrentDictionary<Guid, IAssetEntityEnriched> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntityEnriched>();
private readonly IContentQueryService contentQuery;
private readonly IAssetQueryService assetQuery;
private readonly QueryContext context;
@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.context = context;
}
public virtual async Task<IEnrichedAssetEntity> FindAssetAsync(Guid id)
public virtual async Task<IAssetEntityEnriched> FindAssetAsync(Guid id)
{
var asset = cachedAssets.GetOrDefault(id);
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return result;
}
public virtual async Task<IReadOnlyList<IEnrichedAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids)
public virtual async Task<IReadOnlyList<IAssetEntityEnriched>> GetReferencedAssetsAsync(ICollection<Guid> ids)
{
Guard.NotNull(ids, nameof(ids));

10
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -13,6 +13,16 @@ namespace Squidex.Infrastructure
{
public static class CollectionExtensions
{
public static IResultList<T> SortSet<T, TKey>(this IResultList<T> input, Func<T, TKey> idProvider, IReadOnlyList<TKey> ids) where T : class
{
return ResultList.Create(input.Total, SortList(input, idProvider, ids));
}
public static IEnumerable<T> SortList<T, TKey>(this IEnumerable<T> input, Func<T, TKey> idProvider, IReadOnlyList<TKey> ids) where T : class
{
return ids.Select(id => input.FirstOrDefault(x => Equals(idProvider(x), id))).Where(x => x != null);
}
public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> source)
{
foreach (var value in source)

2
src/Squidex.Infrastructure/ResultList.cs

@ -27,7 +27,7 @@ namespace Squidex.Infrastructure
return new Impl<T>(items, total);
}
public static IResultList<T> Create<T>(long total, params T[] items)
public static IResultList<T> CreateFrom<T>(long total, params T[] items)
{
return new Impl<T>(items, total);
}

2
src/Squidex.Web/UrlHelperExtensions.cs

@ -38,7 +38,7 @@ namespace Squidex.Web
public static string Url<T>(this Controller controller, Func<T, string> action, object values = null) where T : Controller
{
return controller.Url.Url<T>(action, values);
return controller.Url.Url(action, values);
}
}
}

2
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -265,7 +265,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
{
var context = await CommandBus.PublishAsync(command);
var result = context.Result<IEnrichedAssetEntity>();
var result = context.Result<IAssetEntityEnriched>();
var response = AssetDto.FromAsset(result, this, app);
return response;

2
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -118,7 +118,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
[JsonProperty("_meta")]
public AssetMetadata Metadata { get; set; }
public static AssetDto FromAsset(IEnrichedAssetEntity asset, ApiController controller, string app, bool isDuplicate = false)
public static AssetDto FromAsset(IAssetEntityEnriched asset, ApiController controller, string app, bool isDuplicate = false)
{
var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() });

2
src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs

@ -37,7 +37,7 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
return Items.ToSurrogateKeys();
}
public static AssetsDto FromAssets(IResultList<IEnrichedAssetEntity> assets, ApiController controller, string app)
public static AssetsDto FromAssets(IResultList<IAssetEntityEnriched> assets, ApiController controller, string app)
{
var response = new AssetsDto
{

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

@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared;
using Squidex.Web;
@ -127,9 +126,8 @@ namespace Squidex.Areas.Api.Controllers.Contents
{
var context = Context();
var contents = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids);
var contentsList = ResultList.Create<IContentEntity>(contents.Count, contents);
var response = await ContentsDto.FromContentsAsync(contentsList, context, this, null, contentWorkflow);
var response = await ContentsDto.FromContentsAsync(contents, context, this, null, contentWorkflow);
if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys)
{
@ -201,7 +199,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context();
var content = await contentQuery.FindContentAsync(context, name, id);
var response = await ContentDto.FromContentAsync(context, content, contentWorkflow, this);
var response = ContentDto.FromContent(context, content, this);
if (controllerOptions.Value.EnableSurrogateKeys)
{
@ -237,7 +235,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var context = Context();
var content = await contentQuery.FindContentAsync(context, name, id, version);
var response = await ContentDto.FromContentAsync(context, content, contentWorkflow, this);
var response = ContentDto.FromContent(context, content, this);
if (controllerOptions.Value.EnableSurrogateKeys)
{
@ -447,8 +445,8 @@ namespace Squidex.Areas.Api.Controllers.Contents
{
var context = await CommandBus.PublishAsync(command);
var result = context.Result<IContentEntity>();
var response = await ContentDto.FromContentAsync(null, result, contentWorkflow, this);
var result = context.Result<IContentEntityEnriched>();
var response = ContentDto.FromContent(null, result, this);
return response;
}

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

@ -7,7 +7,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
@ -85,11 +84,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary>
public long Version { get; set; }
public static ValueTask<ContentDto> FromContentAsync(
QueryContext context,
IContentEntity content,
IContentWorkflow contentWorkflow,
ApiController controller)
public static ContentDto FromContent(QueryContext context, IContentEntityEnriched content, ApiController controller)
{
var response = SimpleMapper.Map(content, new ContentDto());
@ -109,14 +104,10 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
response.ScheduleJob = SimpleMapper.Map(content.ScheduleJob, new ScheduleJobDto());
}
return response.CreateLinksAsync(content, controller, content.AppId.Name, content.SchemaId.Name, contentWorkflow);
return response.CreateLinksAsync(content, controller, content.AppId.Name, content.SchemaId.Name);
}
private async ValueTask<ContentDto> CreateLinksAsync(IContentEntity content,
ApiController controller,
string app,
string schema,
IContentWorkflow contentWorkflow)
private ContentDto CreateLinksAsync(IContentEntityEnriched content, ApiController controller, string app, string schema)
{
var values = new { app, name = schema, id = Id };
@ -144,7 +135,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
if (controller.HasPermission(Permissions.AppContentsUpdate, app, schema))
{
if (await contentWorkflow.CanUpdateAsync(content))
if (content.CanUpdate)
{
AddPutLink("update", controller.Url<ContentsController>(x => nameof(x.PutContent), values));
}
@ -162,9 +153,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
AddDeleteLink("delete", controller.Url<ContentsController>(x => nameof(x.DeleteContent), values));
}
var nextStatuses = await contentWorkflow.GetNextsAsync(content);
foreach (var next in nextStatuses)
foreach (var next in content.Nexts)
{
if (controller.HasPermission(Helper.StatusPermission(app, schema, next.Status)))
{

20
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -47,20 +47,16 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
return Items.ToSurrogateKeys();
}
public static async Task<ContentsDto> FromContentsAsync(IResultList<IContentEntity> contents, QueryContext context,
ApiController controller,
ISchemaEntity schema,
IContentWorkflow contentWorkflow)
public static async Task<ContentsDto> FromContentsAsync(IResultList<IContentEntityEnriched> contents,
QueryContext context, ApiController controller, ISchemaEntity schema, IContentWorkflow contentWorkflow)
{
var result = new ContentsDto
{
Total = contents.Total,
Items = new ContentDto[contents.Count]
Items = contents.Select(x => ContentDto.FromContent(context, x, controller)).ToArray()
};
await Task.WhenAll(
result.AssignContentsAsync(contentWorkflow, contents, context, controller),
result.AssignStatusesAsync(contentWorkflow, schema));
await result.AssignStatusesAsync(contentWorkflow, schema);
return result.CreateLinks(controller, schema.AppId.Name, schema.SchemaDef.Name);
}
@ -72,14 +68,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
Statuses = allStatuses.Select(StatusInfoDto.FromStatusInfo).ToArray();
}
private async Task AssignContentsAsync(IContentWorkflow contentWorkflow, IResultList<IContentEntity> contents, QueryContext context, ApiController controller)
{
for (var i = 0; i < Items.Length; i++)
{
Items[i] = await ContentDto.FromContentAsync(context, contents[i], contentWorkflow, controller);
}
}
private ContentsDto CreateLinks(ApiController controller, string app, string schema)
{
if (schema != null)

13
src/Squidex/Config/Domain/EntitiesServices.cs

@ -32,7 +32,6 @@ using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.Comments;
using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.Contents.Text;
@ -97,9 +96,15 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppProvider>()
.As<IAppProvider>();
services.AddSingletonAs<AssetEnricher>()
.As<IAssetEnricher>();
services.AddSingletonAs<AssetQueryService>()
.As<IAssetQueryService>();
services.AddSingletonAs<ContentEnricher>()
.As<IContentEnricher>();
services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>();
@ -222,6 +227,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AssetCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<ContentCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<AppsByNameIndexCommandMiddleware>()
.As<ICommandMiddleware>();
@ -231,9 +239,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<GrainCommandMiddleware<CommentsCommand, ICommentGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<ContentCommand, IContentGrain>>()
.As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<SchemaCommand, ISchemaGrain>>()
.As<ICommandMiddleware>();

2
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs

@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
.Returns(assetGrain);
A.CallTo(() => assetGrain.GetStateAsync(12))
.Returns(A.Fake<IAssetEntity>().AsJ());
.Returns(J.Of<IAssetEntity>(new AssetEntity()));
var result = await sut.CreateEnrichedEventAsync(envelope);

84
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs

@ -11,8 +11,10 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Orleans;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Tags;
@ -20,6 +22,7 @@ using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
@ -52,8 +55,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
asset = new AssetGrain(Store, tagService, A.Dummy<ISemanticLog>());
asset.ActivateAsync(Id).Wait();
A.CallTo(() => assetEnricher.EnrichAsync(A<IAssetEntity>.Ignored))
.ReturnsLazily(() => SimpleMapper.Map(asset.Snapshot, new AssetEntity()));
A.CallTo(() => assetQuery.QueryByHashAsync(AppId, A<string>.Ignored))
.Returns(new List<IEnrichedAssetEntity>());
.Returns(new List<IAssetEntityEnriched>());
A.CallTo(() => grainFactory.GetGrain<IAssetGrain>(Id, null))
.Returns(asset);
@ -65,6 +71,40 @@ namespace Squidex.Domain.Apps.Entities.Assets
assetThumbnailGenerator, new[] { tagGenerator });
}
[Fact]
public async Task Should_not_invoke_enricher_for_other_result()
{
var command = CreateCommand(new CreateApp());
var context = CreateContextForCommand(command);
context.Complete(12);
await sut.HandleAsync(context);
A.CallTo(() => assetEnricher.EnrichAsync(A<IAssetEntityEnriched>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_asset_result()
{
var result = new AssetEntity();
var command = CreateCommand(new CreateAsset());
var context = CreateContextForCommand(command);
context.Complete(result);
var enriched = new AssetEntity();
A.CallTo(() => assetEnricher.EnrichAsync(result))
.Returns(enriched);
await sut.HandleAsync(context);
Assert.Equal(enriched, context.Result<IAssetEntityEnriched>());
}
[Fact]
public async Task Create_should_create_domain_object()
{
@ -78,12 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = context.Result<AssetCreatedResult>();
Assert.Equal(assetId, result.Asset.Id);
Assert.Contains("tag1", command.Tags);
Assert.Contains("tag2", command.Tags);
AssertAssetHasBeenUploaded(0, context.ContextId);
AssertAssetImageChecked();
result.Asset.Should().BeEquivalentTo(asset.Snapshot, x => x.ExcludingMissingMembers());
}
[Fact]
@ -132,19 +167,21 @@ namespace Squidex.Domain.Apps.Entities.Assets
}
[Fact]
public async Task Create_should_resolve_tag_names_for_duplicate()
public async Task Create_should_pass_through_duplicate()
{
var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file });
var context = CreateContextForCommand(command);
SetupSameHashAsset(file.FileName, file.FileSize, out _);
SetupSameHashAsset(file.FileName, file.FileSize, out var duplicate);
SetupImageInfo();
await sut.HandleAsync(context);
// var result = context.Result<AssetCreatedResult>();
var result = context.Result<AssetCreatedResult>();
// Assert.Equal(new HashSet<string> { "foundTag1", "foundTag2" }, result.Tags);
Assert.True(result.IsDuplicate);
result.Should().BeEquivalentTo(duplicate, x => x.ExcludingMissingMembers());
}
[Fact]
@ -193,7 +230,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
}
[Fact]
public async Task Update_should_resolve_tags()
public async Task Update_should_enrich_asset()
{
var command = CreateCommand(new UpdateAsset { AssetId = assetId, File = file });
var context = CreateContextForCommand(command);
@ -204,13 +241,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.HandleAsync(context);
// var result = context.Result<AssetResult>();
var result = context.Result<IAssetEntityEnriched>();
// Assert.Equal(new HashSet<string> { "foundTag1", "foundTag2" }, result.Tags);
result.Should().BeEquivalentTo(asset.Snapshot, x => x.ExcludingMissingMembers());
}
[Fact]
public async Task AnnotateAsset_should_resolve_tags()
public async Task AnnotateAsset_should_enrich_asset()
{
var command = CreateCommand(new AnnotateAsset { AssetId = assetId, FileName = "newName" });
var context = CreateContextForCommand(command);
@ -221,9 +258,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.HandleAsync(context);
// var result = context.Result<AssetResult>();
var result = context.Result<IAssetEntityEnriched>();
// Assert.Equal(new HashSet<string> { "foundTag1", "foundTag2" }, result.Tags);
result.Should().BeEquivalentTo(asset.Snapshot, x => x.ExcludingMissingMembers());
}
private Task ExecuteCreateAsync()
@ -253,15 +290,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
.MustHaveHappened();
}
private void SetupSameHashAsset(string fileName, long fileSize, out IEnrichedAssetEntity existing)
private void SetupSameHashAsset(string fileName, long fileSize, out IAssetEntityEnriched duplicate)
{
var temp = existing = A.Fake<IEnrichedAssetEntity>();
A.CallTo(() => temp.FileName).Returns(fileName);
A.CallTo(() => temp.FileSize).Returns(fileSize);
duplicate = new AssetEntity
{
FileName = fileName,
FileSize = fileSize
};
A.CallTo(() => assetQuery.QueryByHashAsync(A<Guid>.Ignored, A<string>.Ignored))
.Returns(new List<IEnrichedAssetEntity> { existing });
.Returns(new List<IAssetEntityEnriched> { duplicate });
}
private void SetupImageInfo()

68
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
@ -15,6 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public class AssetEnricherTests
{
private readonly ITagService tagService = A.Fake<ITagService>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly AssetEnricher sut;
public AssetEnricherTests()
@ -25,11 +29,73 @@ namespace Squidex.Domain.Apps.Entities.Assets
[Fact]
public async Task Should_not_enrich_if_asset_contains_null_tags()
{
var source = new AssetEntity();
var source = new AssetEntity { AppId = appId };
var result = await sut.EnrichAsync(source);
Assert.Empty(result.TagNames);
}
[Fact]
public async Task Should_enrich_asset_with_tag_names()
{
var source = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.IsSameSequenceAs("id1", "id2")))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2"
});
var result = await sut.EnrichAsync(source);
Assert.Equal(new HashSet<string> { "name1", "name2" }, result.TagNames);
}
[Fact]
public async Task Should_enrich_multiple_assets_with_tag_names()
{
var source1 = new AssetEntity
{
Tags = new HashSet<string>
{
"id1",
"id2"
},
AppId = appId
};
var source2 = new AssetEntity
{
Tags = new HashSet<string>
{
"id2",
"id3"
},
AppId = appId
};
A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.IsSameSequenceAs("id1", "id2", "id3")))
.Returns(new Dictionary<string, string>
{
["id1"] = "name1",
["id2"] = "name2",
["id3"] = "name3"
});
var result = await sut.EnrichAsync(new[] { source1, source2 });
Assert.Equal(new HashSet<string> { "name1", "name2" }, result[0].TagNames);
Assert.Equal(new HashSet<string> { "name2", "name3" }, result[1].TagNames);
}
}
}

10
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs

@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
.Returns(new List<IAssetEntity> { found });
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found)))
.Returns(new List<IEnrichedAssetEntity> { enriched });
.Returns(new List<IAssetEntityEnriched> { enriched });
var result = await sut.QueryByHashAsync(appId.Id, "hash");
@ -104,10 +104,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
var ids = HashSet.Of(found1.Id, found2.Id);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<HashSet<Guid>>.That.IsSameSequenceAs(ids)))
.Returns(ResultList.Create(8, found1, found2));
.Returns(ResultList.CreateFrom(8, found1, found2));
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2)))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
.Returns(new List<IAssetEntityEnriched> { enriched1, enriched2 });
var result = await sut.QueryAsync(context, Q.Empty.WithIds(ids));
@ -126,10 +126,10 @@ namespace Squidex.Domain.Apps.Entities.Assets
var enriched2 = new AssetEntity();
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.Ignored))
.Returns(ResultList.Create(8, found1, found2));
.Returns(ResultList.CreateFrom(8, found1, found2));
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2)))
.Returns(new List<IEnrichedAssetEntity> { enriched1, enriched2 });
.Returns(new List<IAssetEntityEnriched> { enriched1, enriched2 });
var result = await sut.QueryAsync(context, Q.Empty);

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs

@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
.Returns(contentGrain);
A.CallTo(() => contentGrain.GetStateAsync(12))
.Returns(A.Fake<IContentEntity>().AsJ());
.Returns(J.Of<IContentEntity>(new ContentEntity { SchemaId = SchemaMatch }));
var result = await sut.CreateEnrichedEventAsync(envelope);

70
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -0,0 +1,70 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Commands;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentState>
{
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly Guid contentId = Guid.NewGuid();
private readonly ContentCommandMiddleware sut;
protected override Guid Id
{
get { return contentId; }
}
public ContentCommandMiddlewareTests()
{
sut = new ContentCommandMiddleware(A.Fake<IGrainFactory>(), contentEnricher);
}
[Fact]
public async Task Should_not_invoke_enricher_for_other_result()
{
var command = CreateCommand(new CreateContent());
var context = CreateContextForCommand(command);
context.Complete(12);
await sut.HandleAsync(context);
A.CallTo(() => contentEnricher.EnrichAsync(A<IContentEntityEnriched>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_enrich_content_result()
{
var result = new ContentEntity();
var command = CreateCommand(new CreateContent());
var context = CreateContextForCommand(command);
context.Complete(result);
var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(result))
.Returns(enriched);
await sut.HandleAsync(context);
Assert.Equal(enriched, context.Result<IContentEntityEnriched>());
}
}
}

85
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs

@ -0,0 +1,85 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentEnricherTests
{
private readonly IContentWorkflow workflow = A.Fake<IContentWorkflow>();
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ContentEnricher sut;
public ContentEnricherTests()
{
sut = new ContentEnricher(workflow);
}
[Fact]
public async Task Should_enrich_content_with_status_color()
{
var source = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(source);
Assert.Equal(StatusColors.Published, result.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_default_color_if_not_found()
{
var source = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.Returns(Task.FromResult<StatusInfo>(null));
var result = await sut.EnrichAsync(source);
Assert.Equal(StatusColors.Draft, result.StatusColor);
}
[Fact]
public async Task Should_enrich_content_with_can_update()
{
var source = new ContentEntity { SchemaId = schemaId };
A.CallTo(() => workflow.CanUpdateAsync(source))
.Returns(true);
var result = await sut.EnrichAsync(source);
Assert.True(result.CanUpdate);
}
[Fact]
public async Task Should_enrich_multiple_contents_and_cache_color()
{
var source1 = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
var source2 = new ContentEntity { Status = Status.Published, SchemaId = schemaId };
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.Returns(new StatusInfo(Status.Published, StatusColors.Published));
var result = await sut.EnrichAsync(new[] { source1, source2 });
Assert.Equal(StatusColors.Published, result[0].StatusColor);
Assert.Equal(StatusColors.Published, result[1].StatusColor);
A.CallTo(() => workflow.GetInfoAsync(Status.Published))
.MustHaveHappenedOnceExactly();
}
}
}

31
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -25,6 +25,7 @@ using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
using Squidex.Shared.Identity;
@ -36,6 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public class ContentQueryServiceTests
{
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IContentVersionLoader contentVersionLoader = A.Fake<IContentVersionLoader>();
private readonly IScriptEngine scriptEngine = A.Fake<IScriptEngine>();
@ -76,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
SetupEnricher();
context = QueryContext.Create(app, user);
var options = Options.Create(new ContentOptions { DefaultPageSize = 30 });
@ -83,6 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
sut = new ContentQueryService(
appProvider,
urlGenerator,
contentEnricher,
contentRepository,
contentVersionLoader,
scriptEngine,
@ -520,6 +525,17 @@ namespace Squidex.Domain.Apps.Entities.Contents
.Returns((ISchemaEntity)null);
}
private void SetupEnricher()
{
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<IContentEntity>>.Ignored))
.ReturnsLazily(x =>
{
var input = (IEnumerable<IContentEntity>)x.Arguments[0];
return Task.FromResult<IReadOnlyList<IContentEntityEnriched>>(input.Select(c => SimpleMapper.Map(c, new ContentEntity())).ToList());
});
}
private IContentEntity CreateContent(Guid id)
{
return CreateContent(id, Status.Published);
@ -527,13 +543,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
private IContentEntity CreateContent(Guid id, Status status)
{
var content = A.Fake<IContentEntity>();
A.CallTo(() => content.Id).Returns(id);
A.CallTo(() => content.Data).Returns(contentData);
A.CallTo(() => content.DataDraft).Returns(contentData);
A.CallTo(() => content.Status).Returns(status);
A.CallTo(() => content.SchemaId).Returns(schemaId);
var content = new ContentEntity
{
Id = id,
Data = contentData,
DataDraft = contentData,
SchemaId = schemaId,
Status = status,
};
return content;
}

18
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task Should_throw_exception_if_no_state_returned()
{
A.CallTo(() => grain.GetStateAsync(10))
.Returns(new J<IContentEntity>(null));
.Returns(J.Of<IContentEntity>(null));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
}
@ -42,13 +42,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_throw_exception_if_state_has_other_version()
{
var entity = A.Fake<IContentEntity>();
A.CallTo(() => entity.Version)
.Returns(5);
var content = new ContentEntity { Version = 5 };
A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity));
.Returns(J.Of<IContentEntity>(content));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
}
@ -56,17 +53,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_return_content_from_state()
{
var entity = A.Fake<IContentEntity>();
A.CallTo(() => entity.Version)
.Returns(10);
var content = new ContentEntity { Version = 10 };
A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity));
.Returns(J.Of<IContentEntity>(content));
var result = await sut.LoadAsync(id, 10);
Assert.Same(entity, result);
Assert.Same(content, result);
}
}
}

32
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Threading.Tasks;
using FakeItEasy;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
using Xunit;
@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_check_is_valid_next()
{
var entity = CreateContent(Status.Published);
var content = new ContentEntity { Status = Status.Published };
var result = await sut.CanMoveToAsync(entity, Status.Draft);
var result = await sut.CanMoveToAsync(content, Status.Draft);
Assert.True(result);
}
@ -40,9 +39,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_be_able_to_update_published()
{
var entity = CreateContent(Status.Published);
var content = new ContentEntity { Status = Status.Published };
var result = await sut.CanUpdateAsync(entity);
var result = await sut.CanUpdateAsync(content);
Assert.True(result);
}
@ -50,9 +49,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_be_able_to_update_draft()
{
var entity = CreateContent(Status.Published);
var content = new ContentEntity { Status = Status.Published };
var result = await sut.CanUpdateAsync(entity);
var result = await sut.CanUpdateAsync(content);
Assert.True(result);
}
@ -60,9 +59,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_not_be_able_to_update_archived()
{
var entity = CreateContent(Status.Archived);
var content = new ContentEntity { Status = Status.Archived };
var result = await sut.CanUpdateAsync(entity);
var result = await sut.CanUpdateAsync(content);
Assert.False(result);
}
@ -70,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_get_next_statuses_for_draft()
{
var content = CreateContent(Status.Draft);
var content = new ContentEntity { Status = Status.Draft };
var expected = new[]
{
@ -86,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_get_next_statuses_for_archived()
{
var content = CreateContent(Status.Archived);
var content = new ContentEntity { Status = Status.Archived };
var expected = new[]
{
@ -101,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact]
public async Task Should_get_next_statuses_for_published()
{
var content = CreateContent(Status.Published);
var content = new ContentEntity { Status = Status.Published };
var expected = new[]
{
@ -128,14 +127,5 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.Should().BeEquivalentTo(expected);
}
private IContentEntity CreateContent(Status status)
{
var content = A.Fake<IContentEntity>();
A.CallTo(() => content.Status).Returns(status);
return content;
}
}
}

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

@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var asset = CreateAsset(Guid.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query")))
.Returns(ResultList.Create(0, asset));
.Returns(ResultList.CreateFrom(0, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -138,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var asset = CreateAsset(Guid.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query")))
.Returns(ResultList.Create(10, asset));
.Returns(ResultList.CreateFrom(10, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -213,7 +213,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", assetId.ToString());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId)))
.Returns(ResultList.Create(1, asset));
.Returns(ResultList.CreateFrom(1, asset));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -302,7 +302,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var content = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5")))
.Returns(ResultList.Create(0, content));
.Returns(ResultList.CreateFrom(0, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -443,7 +443,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var content = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5")))
.Returns(ResultList.Create(10, content));
.Returns(ResultList.CreateFrom(10, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -549,7 +549,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -641,7 +641,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -737,10 +737,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A<Q>.Ignored))
.Returns(ResultList.Create(0, contentRef));
.Returns(ResultList.CreateFrom(0, contentRef));
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -795,10 +795,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), A<Q>.Ignored))
.Returns(ResultList.Create(0, assetRef));
.Returns(ResultList.CreateFrom(0, assetRef));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -851,10 +851,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", assetId2.ToString());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId1)))
.Returns(ResultList.Create(0, asset1));
.Returns(ResultList.CreateFrom(0, asset1));
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), MatchId(assetId2)))
.Returns(ResultList.Create(0, asset2));
.Returns(ResultList.CreateFrom(0, asset2));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query1 }, new GraphQLQuery { Query = query2 });
@ -910,7 +910,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -948,7 +948,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
@ -994,7 +994,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}".Replace("<ID>", contentId.ToString());
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), MatchId(contentId)))
.Returns(ResultList.Create(1, content));
.Returns(ResultList.CreateFrom(1, content));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
sut = CreateSut();
}
protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null, NamedContentData dataDraft = null)
protected static IContentEntityEnriched CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null, NamedContentData dataDraft = null)
{
var now = SystemClock.Instance.GetCurrentInstant();
@ -158,13 +158,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
Data = data,
DataDraft = dataDraft,
Status = Status.Draft,
StatusInfo = new StatusInfo(Status.Draft, "red")
StatusColor = "red"
};
return content;
}
protected static IEnrichedAssetEntity CreateAsset(Guid id)
protected static IAssetEntityEnriched CreateAsset(Guid id)
{
var now = SystemClock.Instance.GetCurrentInstant();

7
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -270,12 +270,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
private IContentEntity CreateContent(Status status, bool isPending)
{
var content = A.Fake<IContentEntity>();
A.CallTo(() => content.Status).Returns(status);
A.CallTo(() => content.IsPending).Returns(isPending);
return content;
return new ContentEntity { Status = status, IsPending = isPending };
}
}
}

Loading…
Cancel
Save