From d7477239fc1b36fb4b88020c55432faab4c02397 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 24 Jun 2019 20:12:21 +0200 Subject: [PATCH] Enrichment for status color. --- .../Contents/MongoContentRepository.cs | 2 +- .../Assets/AssetCommandMiddleware.cs | 53 +++---- .../Assets/AssetCreatedResult.cs | 4 +- .../Assets/AssetEnricher.cs | 40 ++--- .../Assets/AssetEntity.cs | 2 +- .../Assets/AssetQueryService.cs | 15 +- .../Assets/IAssetEnricher.cs | 4 +- ...AssetEntity.cs => IAssetEntityEnriched.cs} | 2 +- .../Assets/IAssetQueryService.cs | 6 +- .../Contents/ContentCommandMiddleware.cs | 41 +++++ .../Contents/ContentEnricher.cs | 60 ++++++-- .../Contents/ContentEntity.cs | 8 +- .../Contents/ContentQueryService.cs | 144 ++++++++++-------- .../GraphQL/GraphQLExecutionContext.cs | 10 +- .../Contents/GraphQL/Types/AllTypes.cs | 1 - .../Contents/GraphQL/Types/AssetGraphType.cs | 8 +- .../GraphQL/Types/ContentGraphType.cs | 8 +- .../Contents/IContentEnricher.cs | 4 +- ...entEntity.cs => IContentEntityEnriched.cs} | 6 +- .../Contents/IContentQueryService.cs | 6 +- .../Contents/QueryExecutionContext.cs | 6 +- .../CollectionExtensions.cs | 10 ++ src/Squidex.Infrastructure/ResultList.cs | 2 +- src/Squidex.Web/UrlHelperExtensions.cs | 2 +- .../Controllers/Assets/AssetsController.cs | 2 +- .../Api/Controllers/Assets/Models/AssetDto.cs | 2 +- .../Controllers/Assets/Models/AssetsDto.cs | 2 +- .../Contents/ContentsController.cs | 12 +- .../Controllers/Contents/Models/ContentDto.cs | 21 +-- .../Contents/Models/ContentsDto.cs | 20 +-- src/Squidex/Config/Domain/EntitiesServices.cs | 13 +- .../Assets/AssetChangedTriggerHandlerTests.cs | 2 +- .../Assets/AssetCommandMiddlewareTests.cs | 84 +++++++--- .../Assets/AssetEnricherTests.cs | 68 ++++++++- .../Assets/AssetQueryServiceTests.cs | 10 +- .../ContentChangedTriggerHandlerTests.cs | 2 +- .../Contents/ContentCommandMiddlewareTests.cs | 70 +++++++++ .../Contents/ContentEnricherTests.cs | 85 +++++++++++ .../Contents/ContentQueryServiceTests.cs | 31 +++- .../Contents/ContentVersionLoaderTests.cs | 18 +-- .../Contents/DefaultContentWorkflowTests.cs | 32 ++-- .../Contents/GraphQL/GraphQLQueriesTests.cs | 32 ++-- .../Contents/GraphQL/GraphQLTestBase.cs | 6 +- .../Contents/Guard/GuardContentTests.cs | 7 +- 44 files changed, 645 insertions(+), 318 deletions(-) rename src/Squidex.Domain.Apps.Entities/Assets/{IEnrichedAssetEntity.cs => IAssetEntityEnriched.cs} (89%) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs rename src/Squidex.Domain.Apps.Entities/Contents/{IEnrichedContentEntity.cs => IContentEntityEnriched.cs} (79%) create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index aa24583e6..51e7ca5a9 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/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(0); + return ResultList.CreateFrom(0); } return await contents.QueryAsync(schema, query, fullTextIds, status, inDraft, includeDraft); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index a9b1e3c31..9be779afa 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/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 ExecuteAndAdjustTagsAsync(AssetCommand command) + private async Task HandleCoreAsync(CommandContext context, Func 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) diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs index aa932bf36..c87c58260 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs +++ b/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; diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetEnricher.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetEnricher.cs index fd912a892..56d660b8c 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetEnricher.cs +++ b/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 EnrichAsync(IAssetEntity asset) + public async Task EnrichAsync(IAssetEntity asset) { Guard.NotNull(asset, nameof(asset)); @@ -34,38 +35,41 @@ namespace Squidex.Domain.Apps.Entities.Assets return enriched[0]; } - public async Task> EnrichAsync(IEnumerable assets) + public async Task> EnrichAsync(IEnumerable assets) { Guard.NotNull(assets, nameof(assets)); - var results = new List(); - - foreach (var group in assets.GroupBy(x => x.AppId.Id)) + using (Profiler.TraceMethod()) { - var tagsById = await CalculateTags(group); + var results = new List(); - 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(); + 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(); + + 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> CalculateTags(IGrouping group) diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs index 150e53b78..1d2904eca 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs +++ b/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 AppId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs index 1087fcbe0..07e2c0da8 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs +++ b/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 FindAssetAsync( Guid id) + public async Task FindAssetAsync( Guid id) { var asset = await assetRepository.FindAssetAsync(id); @@ -62,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Assets return null; } - public async Task> QueryByHashAsync(Guid appId, string hash) + public async Task> 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> QueryAsync(QueryContext context, Q query) + public async Task> QueryAsync(QueryContext context, Q query) { Guard.NotNull(context, nameof(context)); Guard.NotNull(query, nameof(query)); IResultList 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(assets.Total, enriched); + return ResultList.Create(assets.Total, enriched); } private async Task> QueryByQueryAsync(QueryContext context, Q query) @@ -108,9 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Assets private static IResultList Sort(IResultList assets, IReadOnlyList 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) diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs index 1807af316..8c485c234 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEnricher.cs @@ -12,8 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetEnricher { - Task EnrichAsync(IAssetEntity asset); + Task EnrichAsync(IAssetEntity asset); - Task> EnrichAsync(IEnumerable assets); + Task> EnrichAsync(IEnumerable assets); } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntityEnriched.cs similarity index 89% rename from src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs rename to src/Squidex.Domain.Apps.Entities/Assets/IAssetEntityEnriched.cs index eab0cde16..53bbf6249 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs +++ b/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 TagNames { get; } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs index a186e376c..5e7ce35f0 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs @@ -16,10 +16,10 @@ namespace Squidex.Domain.Apps.Entities.Assets { int DefaultPageSizeGraphQl { get; } - Task> QueryByHashAsync(Guid appId, string hash); + Task> QueryByHashAsync(Guid appId, string hash); - Task> QueryAsync(QueryContext contex, Q query); + Task> QueryAsync(QueryContext contex, Q query); - Task FindAssetAsync(Guid id); + Task FindAssetAsync(Guid id); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs new file mode 100644 index 000000000..3178216fd --- /dev/null +++ b/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 + { + 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 next) + { + await HandleAsync(context, next); + + if (context.PlainResult is IContentEntity content) + { + var enriched = await contentEnricher.EnrichAsync(content); + + context.Complete(enriched); + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs index 109b20b54..428506c61 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs +++ b/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> EnrichAsync(IEnumerable contents) + public async Task EnrichAsync(IContentEntity content) { - var results = new List(); + Guard.NotNull(content, nameof(content)); + + var enriched = await EnrichAsync(Enumerable.Repeat(content, 1)); + + return enriched[0]; + } + + public async Task> EnrichAsync(IEnumerable contents) + { + Guard.NotNull(contents, nameof(contents)); using (Profiler.TraceMethod()) { - var cache = new Dictionary(); + var results = new List(); + + 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; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs index 180e1cf9d..4be05e9da 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs +++ b/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; } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index b67536690..8d2deb6ad 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/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 EmptyContents = ResultList.CreateFrom(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 FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = -1) + public async Task 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()) { - 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> QueryAsync(QueryContext context, string schemaIdOrName, Q query) + public async Task> 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()) { - var status = GetStatus(context); - IResultList 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> QueryAsync(QueryContext context, IReadOnlyList ids) + public async Task> QueryAsync(QueryContext context, IReadOnlyList ids) { Guard.NotNull(context, nameof(context)); using (Profiler.TraceMethod()) { - var status = GetStatus(context); - - List 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(); - 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(); + 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 Transform(QueryContext context, ISchemaEntity schema, IResultList contents) + private async Task> TransformAsync(QueryContext context, ISchemaEntity schema, IResultList 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 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 SortSet(IResultList contents, IReadOnlyList ids) - { - return ResultList.Create(contents.Total, SortList(contents, ids)); + return transformed[0]; } - private static IEnumerable SortList(IEnumerable contents, IReadOnlyList ids) - { - return ids.Select(id => contents.FirstOrDefault(x => x.Id == id)).Where(x => x != null); - } - - private IEnumerable TransformCore(QueryContext context, ISchemaEntity schema, IEnumerable contents) + private async Task> TransformCoreAsync(QueryContext context, ISchemaEntity schema, IEnumerable contents) { using (Profiler.TraceMethod()) { + var results = new List(); + 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> QueryAsync(QueryContext context, IReadOnlyList ids, Status[] status) + private async Task> QueryByQueryAsync(QueryContext context, Q query, ISchemaEntity schema) + { + var parsedQuery = ParseQuery(context, query.ODataQuery, schema); + + return await QueryCoreAsync(context, schema, parsedQuery); + } + + private async Task> 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> QueryCoreAsync(QueryContext context, IReadOnlyList ids) { - return contentRepository.QueryAsync(context.App, status, new HashSet(ids), ShouldIncludeDraft(context)); + return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet(ids), WithDraft(context)); } - private Task> QueryAsync(QueryContext context, ISchemaEntity schema, Query query, Status[] status) + private Task> 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> QueryAsync(QueryContext context, ISchemaEntity schema, HashSet ids, Status[] status) + private Task> QueryCoreAsync(QueryContext context, ISchemaEntity schema, HashSet ids) { - return contentRepository.QueryAsync(context.App, schema, status, ids, ShouldIncludeDraft(context)); + return contentRepository.QueryAsync(context.App, schema, GetStatus(context), ids, WithDraft(context)); } - private Task FindContentAsync(QueryContext context, Guid id, Status[] status, ISchemaEntity schema) + private Task 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 FindContentByVersionAsync(Guid id, long version) + private Task 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; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index db7e2859c..52082bbf6 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs +++ b/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 EmptyAssets = new List(); + private static readonly List EmptyAssets = new List(); private static readonly List EmptyContents = new List(); 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 FindAssetAsync(Guid id) + public override Task FindAssetAsync(Guid id) { var dataLoader = GetAssetsLoader(); @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return dataLoader.LoadAsync(id); } - public async Task> GetReferencedAssetsAsync(IJsonValue value) + public async Task> 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 GetAssetsLoader() + private IDataLoader GetAssetsLoader() { - return dataLoaderContextAccessor.Context.GetOrAddBatchLoader("Assets", + return dataLoaderContextAccessor.Context.GetOrAddBatchLoader("Assets", async batch => { var result = await GetReferencedAssetsAsync(new List(batch)); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs index 8f3207ebf..c8ee5d2f2 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs +++ b/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 diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index 0c8c598b4..329e27814 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs +++ b/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 + public sealed class AssetGraphType : ObjectGraphType { 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 action) + private static IFieldResolver Resolve(Func action) { - return new FuncFieldResolver(c => action(c.Source)); + return new FuncFieldResolver(c => action(c.Source)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index d13328264..e8f35ae05 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs +++ b/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 + public sealed class ContentGraphType : ObjectGraphType { 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 action) + private static IFieldResolver Resolve(Func action) { - return new FuncFieldResolver(c => action(c.Source)); + return new FuncFieldResolver(c => action(c.Source)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs index 2cfe42f3a..abbfc7f45 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs @@ -12,6 +12,8 @@ namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentEnricher { - Task> EnrichAsync(IEnumerable contents); + Task EnrichAsync(IContentEntity content); + + Task> EnrichAsync(IEnumerable contents); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntityEnriched.cs similarity index 79% rename from src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs rename to src/Squidex.Domain.Apps.Entities/Contents/IContentEntityEnriched.cs index af37918f5..0a8b72a28 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs +++ b/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; } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index 769422de1..6d49fb462 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -17,11 +17,11 @@ namespace Squidex.Domain.Apps.Entities.Contents { int DefaultPageSizeGraphQl { get; } - Task> QueryAsync(QueryContext context, IReadOnlyList ids); + Task> QueryAsync(QueryContext context, IReadOnlyList ids); - Task> QueryAsync(QueryContext context, string schemaIdOrName, Q query); + Task> QueryAsync(QueryContext context, string schemaIdOrName, Q query); - Task FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any); + Task FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any); Task GetSchemaOrThrowAsync(QueryContext context, string schemaIdOrName); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs index a40896a94..78a2b23c9 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents public class QueryExecutionContext { private readonly ConcurrentDictionary cachedContents = new ConcurrentDictionary(); - private readonly ConcurrentDictionary cachedAssets = new ConcurrentDictionary(); + private readonly ConcurrentDictionary cachedAssets = new ConcurrentDictionary(); 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 FindAssetAsync(Guid id) + public virtual async Task FindAssetAsync(Guid id) { var asset = cachedAssets.GetOrDefault(id); @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Contents return result; } - public virtual async Task> GetReferencedAssetsAsync(ICollection ids) + public virtual async Task> GetReferencedAssetsAsync(ICollection ids) { Guard.NotNull(ids, nameof(ids)); diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs index cfe546a24..a8f44e515 100644 --- a/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -13,6 +13,16 @@ namespace Squidex.Infrastructure { public static class CollectionExtensions { + public static IResultList SortSet(this IResultList input, Func idProvider, IReadOnlyList ids) where T : class + { + return ResultList.Create(input.Total, SortList(input, idProvider, ids)); + } + + public static IEnumerable SortList(this IEnumerable input, Func idProvider, IReadOnlyList ids) where T : class + { + return ids.Select(id => input.FirstOrDefault(x => Equals(idProvider(x), id))).Where(x => x != null); + } + public static void AddRange(this ICollection target, IEnumerable source) { foreach (var value in source) diff --git a/src/Squidex.Infrastructure/ResultList.cs b/src/Squidex.Infrastructure/ResultList.cs index 957ecc48b..7f835704f 100644 --- a/src/Squidex.Infrastructure/ResultList.cs +++ b/src/Squidex.Infrastructure/ResultList.cs @@ -27,7 +27,7 @@ namespace Squidex.Infrastructure return new Impl(items, total); } - public static IResultList Create(long total, params T[] items) + public static IResultList CreateFrom(long total, params T[] items) { return new Impl(items, total); } diff --git a/src/Squidex.Web/UrlHelperExtensions.cs b/src/Squidex.Web/UrlHelperExtensions.cs index 27f00a1d9..a4bc9280d 100644 --- a/src/Squidex.Web/UrlHelperExtensions.cs +++ b/src/Squidex.Web/UrlHelperExtensions.cs @@ -38,7 +38,7 @@ namespace Squidex.Web public static string Url(this Controller controller, Func action, object values = null) where T : Controller { - return controller.Url.Url(action, values); + return controller.Url.Url(action, values); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 75b9867a7..7c6005e48 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/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(); + var result = context.Result(); var response = AssetDto.FromAsset(result, this, app); return response; diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index 5c996cf0d..6858b3a91 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/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() }); diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs index efd81147b..9d0900db6 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs +++ b/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 assets, ApiController controller, string app) + public static AssetsDto FromAssets(IResultList assets, ApiController controller, string app) { var response = new AssetsDto { diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 833c67dbe..73645d16f 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/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(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(); - var response = await ContentDto.FromContentAsync(null, result, contentWorkflow, this); + var result = context.Result(); + var response = ContentDto.FromContent(null, result, this); return response; } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index c53a154f8..c91e04e79 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/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 /// public long Version { get; set; } - public static ValueTask 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 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(x => nameof(x.PutContent), values)); } @@ -162,9 +153,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models AddDeleteLink("delete", controller.Url(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))) { diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index 23707b311..765fc8027 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/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 FromContentsAsync(IResultList contents, QueryContext context, - ApiController controller, - ISchemaEntity schema, - IContentWorkflow contentWorkflow) + public static async Task FromContentsAsync(IResultList 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 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) diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index d6965d669..ce886628c 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/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() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); @@ -222,6 +227,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); @@ -231,9 +239,6 @@ namespace Squidex.Config.Domain services.AddSingletonAs>() .As(); - services.AddSingletonAs>() - .As(); - services.AddSingletonAs>() .As(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs index 48f125d12..fc4711887 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs +++ b/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().AsJ()); + .Returns(J.Of(new AssetEntity())); var result = await sut.CreateEnrichedEventAsync(envelope); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index 772bc2128..d43d2f623 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/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()); asset.ActivateAsync(Id).Wait(); + A.CallTo(() => assetEnricher.EnrichAsync(A.Ignored)) + .ReturnsLazily(() => SimpleMapper.Map(asset.Snapshot, new AssetEntity())); + A.CallTo(() => assetQuery.QueryByHashAsync(AppId, A.Ignored)) - .Returns(new List()); + .Returns(new List()); A.CallTo(() => grainFactory.GetGrain(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.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()); + } + [Fact] public async Task Create_should_create_domain_object() { @@ -78,12 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Assets var result = context.Result(); - 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(); + var result = context.Result(); - // Assert.Equal(new HashSet { "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(); + var result = context.Result(); - // Assert.Equal(new HashSet { "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(); + var result = context.Result(); - // Assert.Equal(new HashSet { "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(); - - A.CallTo(() => temp.FileName).Returns(fileName); - A.CallTo(() => temp.FileSize).Returns(fileSize); + duplicate = new AssetEntity + { + FileName = fileName, + FileSize = fileSize + }; A.CallTo(() => assetQuery.QueryByHashAsync(A.Ignored, A.Ignored)) - .Returns(new List { existing }); + .Returns(new List { duplicate }); } private void SetupImageInfo() diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs index f5abf2f59..751098d67 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetEnricherTests.cs +++ b/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(); + private readonly NamedId 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 + { + "id1", + "id2" + }, + AppId = appId + }; + + A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A>.That.IsSameSequenceAs("id1", "id2"))) + .Returns(new Dictionary + { + ["id1"] = "name1", + ["id2"] = "name2" + }); + + var result = await sut.EnrichAsync(source); + + Assert.Equal(new HashSet { "name1", "name2" }, result.TagNames); + } + + [Fact] + public async Task Should_enrich_multiple_assets_with_tag_names() + { + var source1 = new AssetEntity + { + Tags = new HashSet + { + "id1", + "id2" + }, + AppId = appId + }; + + var source2 = new AssetEntity + { + Tags = new HashSet + { + "id2", + "id3" + }, + AppId = appId + }; + + A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A>.That.IsSameSequenceAs("id1", "id2", "id3"))) + .Returns(new Dictionary + { + ["id1"] = "name1", + ["id2"] = "name2", + ["id3"] = "name3" + }); + + var result = await sut.EnrichAsync(new[] { source1, source2 }); + + Assert.Equal(new HashSet { "name1", "name2" }, result[0].TagNames); + Assert.Equal(new HashSet { "name2", "name3" }, result[1].TagNames); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs index cba6ea208..ac845ef8e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs @@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Assets .Returns(new List { found }); A.CallTo(() => assetEnricher.EnrichAsync(A>.That.IsSameSequenceAs(found))) - .Returns(new List { enriched }); + .Returns(new List { 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>.That.IsSameSequenceAs(ids))) - .Returns(ResultList.Create(8, found1, found2)); + .Returns(ResultList.CreateFrom(8, found1, found2)); A.CallTo(() => assetEnricher.EnrichAsync(A>.That.IsSameSequenceAs(found1, found2))) - .Returns(new List { enriched1, enriched2 }); + .Returns(new List { 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.Ignored)) - .Returns(ResultList.Create(8, found1, found2)); + .Returns(ResultList.CreateFrom(8, found1, found2)); A.CallTo(() => assetEnricher.EnrichAsync(A>.That.IsSameSequenceAs(found1, found2))) - .Returns(new List { enriched1, enriched2 }); + .Returns(new List { enriched1, enriched2 }); var result = await sut.QueryAsync(context, Q.Empty); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs index 3328123a7..b943249a8 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs +++ b/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().AsJ()); + .Returns(J.Of(new ContentEntity { SchemaId = SchemaMatch })); var result = await sut.CreateEnrichedEventAsync(envelope); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs new file mode 100644 index 000000000..f375ef589 --- /dev/null +++ b/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 + { + private readonly IContentEnricher contentEnricher = A.Fake(); + private readonly Guid contentId = Guid.NewGuid(); + private readonly ContentCommandMiddleware sut; + + protected override Guid Id + { + get { return contentId; } + } + + public ContentCommandMiddlewareTests() + { + sut = new ContentCommandMiddleware(A.Fake(), 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.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()); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs new file mode 100644 index 000000000..f7ba94467 --- /dev/null +++ b/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(); + private readonly NamedId 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(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(); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index e6fb4334b..909486646 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/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(); private readonly IContentRepository contentRepository = A.Fake(); private readonly IContentVersionLoader contentVersionLoader = A.Fake(); private readonly IScriptEngine scriptEngine = A.Fake(); @@ -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>.Ignored)) + .ReturnsLazily(x => + { + var input = (IEnumerable)x.Arguments[0]; + + return Task.FromResult>(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(); - - 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; } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs index 9b0c35c1c..b6b1d43b6 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs +++ b/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(null)); + .Returns(J.Of(null)); await Assert.ThrowsAsync(() => 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(); - - A.CallTo(() => entity.Version) - .Returns(5); + var content = new ContentEntity { Version = 5 }; A.CallTo(() => grain.GetStateAsync(10)) - .Returns(J.Of(entity)); + .Returns(J.Of(content)); await Assert.ThrowsAsync(() => 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(); - - A.CallTo(() => entity.Version) - .Returns(10); + var content = new ContentEntity { Version = 10 }; A.CallTo(() => grain.GetStateAsync(10)) - .Returns(J.Of(entity)); + .Returns(J.Of(content)); var result = await sut.LoadAsync(id, 10); - Assert.Same(entity, result); + Assert.Same(content, result); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs index 0832ec984..20de4f522 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs +++ b/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(); - - A.CallTo(() => content.Status).Returns(status); - - return content; - } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index 6b93b51b0..82cd8b87e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/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.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.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("", 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.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.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("", 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("", 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("", contentId.ToString()); A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.ToString(), A.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("", 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.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("", 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("", 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("", 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("", 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 }); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index aa8659fcd..6edf39436 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/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(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index 93b96243b..ec1946256 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs +++ b/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(); - - A.CallTo(() => content.Status).Returns(status); - A.CallTo(() => content.IsPending).Returns(isPending); - - return content; + return new ContentEntity { Status = status, IsPending = isPending }; } } }