mirror of https://github.com/Squidex/squidex.git
Browse Source
* Splitted the enrichment in several steps.
* Get rid of unused content.
* Improved value converter.
* Content enrichment improvements.
* Naming improved.
* Auto implementation reverted.
* Tests fixed.
* Index fixes.
* FIx to queries.
* Cleanup.
* Revert "Cleanup."
This reverts commit 4b59ce937c.
* Tests fixed.
pull/481/head
committed by
GitHub
77 changed files with 1848 additions and 1421 deletions
@ -1,52 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Threading.Tasks; |
|
||||
using Squidex.Domain.Apps.Events.Assets; |
|
||||
using Squidex.Domain.Apps.Events.Contents; |
|
||||
using Squidex.Infrastructure.EventSourcing; |
|
||||
using Squidex.Infrastructure.Tasks; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
|
||||
{ |
|
||||
public partial class MongoContentRepository : IEventConsumer |
|
||||
{ |
|
||||
public string Name |
|
||||
{ |
|
||||
get { return GetType().Name; } |
|
||||
} |
|
||||
|
|
||||
public string EventsFilter |
|
||||
{ |
|
||||
get { return "^(content-)|(asset-)"; } |
|
||||
} |
|
||||
|
|
||||
public bool Handles(StoredEvent @event) |
|
||||
{ |
|
||||
return @event.Data.Type == typeAssetDeleted || @event.Data.Type == typeContentDeleted; |
|
||||
} |
|
||||
|
|
||||
public Task On(Envelope<IEvent> @event) |
|
||||
{ |
|
||||
switch (@event.Payload) |
|
||||
{ |
|
||||
case AssetDeleted e: |
|
||||
return cleanupReferences.DoAsync(e.AssetId); |
|
||||
|
|
||||
case ContentDeleted e: |
|
||||
return cleanupReferences.DoAsync(e.ContentId); |
|
||||
} |
|
||||
|
|
||||
return TaskHelper.Done; |
|
||||
} |
|
||||
|
|
||||
Task IEventConsumer.ClearAsync() |
|
||||
{ |
|
||||
return TaskHelper.Done; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,35 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using MongoDB.Driver; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations |
|
||||
{ |
|
||||
internal sealed class CleanupReferences : OperationBase |
|
||||
{ |
|
||||
protected override Task PrepareAsync(CancellationToken ct = default) |
|
||||
{ |
|
||||
var index = |
|
||||
new CreateIndexModel<MongoContentEntity>( |
|
||||
Index.Ascending(x => x.ReferencedIds)); |
|
||||
|
|
||||
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct); |
|
||||
} |
|
||||
|
|
||||
public Task DoAsync(Guid id) |
|
||||
{ |
|
||||
return Collection.UpdateManyAsync( |
|
||||
Filter.And( |
|
||||
Filter.AnyEq(x => x.ReferencedIds, id), |
|
||||
Filter.AnyNe(x => x.ReferencedIdsDeleted, id)), |
|
||||
Update.AddToSet(x => x.ReferencedIdsDeleted, id)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries |
||||
|
{ |
||||
|
public delegate Task<ISchemaEntity> ProvideSchema(Guid id); |
||||
|
|
||||
|
public interface IContentEnricherStep |
||||
|
{ |
||||
|
Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,158 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Domain.Apps.Core.ExtractReferenceIds; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps |
||||
|
{ |
||||
|
public sealed class ConvertData : IContentEnricherStep |
||||
|
{ |
||||
|
private readonly IAssetUrlGenerator assetUrlGenerator; |
||||
|
private readonly IAssetRepository assetRepository; |
||||
|
private readonly IContentRepository contentRepository; |
||||
|
|
||||
|
public ConvertData(IAssetUrlGenerator assetUrlGenerator, IAssetRepository assetRepository, IContentRepository contentRepository) |
||||
|
{ |
||||
|
Guard.NotNull(assetUrlGenerator); |
||||
|
Guard.NotNull(assetRepository); |
||||
|
Guard.NotNull(contentRepository); |
||||
|
|
||||
|
this.assetUrlGenerator = assetUrlGenerator; |
||||
|
this.assetRepository = assetRepository; |
||||
|
this.contentRepository = contentRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
var resolveDataDraft = context.ShouldProvideUnpublished() || context.IsFrontendClient; |
||||
|
|
||||
|
var referenceCleaner = await CleanReferencesAsync(context, contents, schemas); |
||||
|
|
||||
|
var converters = GenerateConverters(context, referenceCleaner).ToArray(); |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
foreach (var content in group) |
||||
|
{ |
||||
|
if (content.Data != null) |
||||
|
{ |
||||
|
content.Data = content.Data.ConvertName2Name(schema.SchemaDef, converters); |
||||
|
} |
||||
|
|
||||
|
if (content.DataDraft != null && resolveDataDraft) |
||||
|
{ |
||||
|
content.DataDraft = content.DataDraft.ConvertName2Name(schema.SchemaDef, converters); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
content.DataDraft = null!; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<ValueConverter?> CleanReferencesAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
if (context.ShouldCleanup()) |
||||
|
{ |
||||
|
var ids = new HashSet<Guid>(); |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
foreach (var content in group) |
||||
|
{ |
||||
|
content.Data?.AddReferencedIds(schema.SchemaDef, ids); |
||||
|
content.DataDraft?.AddReferencedIds(schema.SchemaDef, ids); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (ids.Count > 0) |
||||
|
{ |
||||
|
var taskForAssets = QueryAssetIdsAsync(context, ids); |
||||
|
var taskForContents = QueryContentIdsAsync(context, ids); |
||||
|
|
||||
|
await Task.WhenAll(taskForAssets, taskForContents); |
||||
|
|
||||
|
var foundIds = new HashSet<Guid>(taskForAssets.Result.Union(taskForContents.Result)); |
||||
|
|
||||
|
return ValueReferencesConverter.CleanReferences(foundIds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private async Task<IEnumerable<Guid>> QueryContentIdsAsync(Context context, HashSet<Guid> ids) |
||||
|
{ |
||||
|
var result = await contentRepository.QueryIdsAsync(context.App.Id, ids); |
||||
|
|
||||
|
return result.Select(x => x.Id); |
||||
|
} |
||||
|
|
||||
|
private async Task<IEnumerable<Guid>> QueryAssetIdsAsync(Context context, HashSet<Guid> ids) |
||||
|
{ |
||||
|
var result = await assetRepository.QueryIdsAsync(context.App.Id, ids); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private IEnumerable<FieldConverter> GenerateConverters(Context context, ValueConverter? cleanReferences) |
||||
|
{ |
||||
|
if (!context.IsFrontendClient) |
||||
|
{ |
||||
|
yield return FieldConverters.ExcludeHidden(); |
||||
|
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden()); |
||||
|
} |
||||
|
|
||||
|
yield return FieldConverters.ExcludeChangedTypes(); |
||||
|
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes()); |
||||
|
|
||||
|
if (cleanReferences != null) |
||||
|
{ |
||||
|
yield return FieldConverters.ForValues(cleanReferences); |
||||
|
yield return FieldConverters.ForNestedName2Name(cleanReferences); |
||||
|
} |
||||
|
|
||||
|
yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig); |
||||
|
yield return FieldConverters.ResolveLanguages(context.App.LanguagesConfig); |
||||
|
|
||||
|
if (!context.IsFrontendClient) |
||||
|
{ |
||||
|
if (context.ShouldResolveLanguages()) |
||||
|
{ |
||||
|
yield return FieldConverters.ResolveFallbackLanguages(context.App.LanguagesConfig); |
||||
|
} |
||||
|
|
||||
|
var languages = context.Languages(); |
||||
|
|
||||
|
if (languages.Any()) |
||||
|
{ |
||||
|
yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, languages); |
||||
|
} |
||||
|
|
||||
|
var assetUrls = context.AssetUrls(); |
||||
|
|
||||
|
if (assetUrls.Any()) |
||||
|
{ |
||||
|
yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), assetUrlGenerator); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps |
||||
|
{ |
||||
|
public sealed class EnrichForCaching : IContentEnricherStep |
||||
|
{ |
||||
|
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
var app = context.App; |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
foreach (var content in group) |
||||
|
{ |
||||
|
content.CacheDependencies ??= new HashSet<object?>(); |
||||
|
|
||||
|
content.CacheDependencies.Add(app.Id); |
||||
|
content.CacheDependencies.Add(app.Version); |
||||
|
content.CacheDependencies.Add(schema.Id); |
||||
|
content.CacheDependencies.Add(schema.Version); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps |
||||
|
{ |
||||
|
public sealed class EnrichWithSchema : IContentEnricherStep |
||||
|
{ |
||||
|
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
var schemaName = schema.SchemaDef.Name; |
||||
|
var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged(); |
||||
|
|
||||
|
foreach (var content in group) |
||||
|
{ |
||||
|
content.SchemaName = schemaName; |
||||
|
content.SchemaDisplayName = schemaDisplayName; |
||||
|
} |
||||
|
|
||||
|
if (context.IsFrontendClient) |
||||
|
{ |
||||
|
var referenceFields = schema.SchemaDef.ReferencesFields().ToArray(); |
||||
|
|
||||
|
foreach (var content in group) |
||||
|
{ |
||||
|
content.ReferenceFields = referenceFields; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps |
||||
|
{ |
||||
|
public sealed class EnrichWithWorkflows : IContentEnricherStep |
||||
|
{ |
||||
|
private const string DefaultColor = StatusColors.Draft; |
||||
|
|
||||
|
private readonly IContentWorkflow contentWorkflow; |
||||
|
|
||||
|
public EnrichWithWorkflows(IContentWorkflow contentWorkflow) |
||||
|
{ |
||||
|
Guard.NotNull(contentWorkflow); |
||||
|
|
||||
|
this.contentWorkflow = contentWorkflow; |
||||
|
} |
||||
|
|
||||
|
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
var cache = new Dictionary<(Guid, Status), StatusInfo>(); |
||||
|
|
||||
|
foreach (var content in contents) |
||||
|
{ |
||||
|
await EnrichColorAsync(content, content, cache); |
||||
|
|
||||
|
if (ShouldEnrichWithStatuses(context)) |
||||
|
{ |
||||
|
await EnrichNextsAsync(content, context); |
||||
|
await EnrichCanUpdateAsync(content, context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task EnrichNextsAsync(ContentEntity content, Context context) |
||||
|
{ |
||||
|
content.Nexts = await contentWorkflow.GetNextsAsync(content, context.User); |
||||
|
} |
||||
|
|
||||
|
private async Task EnrichCanUpdateAsync( ContentEntity content, Context context) |
||||
|
{ |
||||
|
content.CanUpdate = await contentWorkflow.CanUpdateAsync(content, context.User); |
||||
|
} |
||||
|
|
||||
|
private async Task EnrichColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache) |
||||
|
{ |
||||
|
result.StatusColor = await GetColorAsync(content, cache); |
||||
|
} |
||||
|
|
||||
|
private async Task<string> GetColorAsync(IContentEntity content, Dictionary<(Guid, Status), StatusInfo> cache) |
||||
|
{ |
||||
|
if (!cache.TryGetValue((content.SchemaId.Id, content.Status), out var info)) |
||||
|
{ |
||||
|
info = await contentWorkflow.GetInfoAsync(content); |
||||
|
|
||||
|
if (info == null) |
||||
|
{ |
||||
|
info = new StatusInfo(content.Status, DefaultColor); |
||||
|
} |
||||
|
|
||||
|
cache[(content.SchemaId.Id, content.Status)] = info; |
||||
|
} |
||||
|
|
||||
|
return info.Color; |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldEnrichWithStatuses(Context context) |
||||
|
{ |
||||
|
return context.IsFrontendClient || context.ShouldResolveFlow(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,131 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Assets; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Domain.Apps.Core.ExtractReferenceIds; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Assets; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps |
||||
|
{ |
||||
|
public sealed class ResolveAssets : IContentEnricherStep |
||||
|
{ |
||||
|
private static readonly ILookup<Guid, IEnrichedAssetEntity> EmptyAssets = Enumerable.Empty<IEnrichedAssetEntity>().ToLookup(x => x.Id); |
||||
|
|
||||
|
private readonly IAssetUrlGenerator assetUrlGenerator; |
||||
|
private readonly IAssetQueryService assetQuery; |
||||
|
|
||||
|
public ResolveAssets(IAssetUrlGenerator assetUrlGenerator, IAssetQueryService assetQuery) |
||||
|
{ |
||||
|
Guard.NotNull(assetUrlGenerator); |
||||
|
Guard.NotNull(assetQuery); |
||||
|
|
||||
|
this.assetUrlGenerator = assetUrlGenerator; |
||||
|
this.assetQuery = assetQuery; |
||||
|
} |
||||
|
|
||||
|
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
if (ShouldEnrich(context)) |
||||
|
{ |
||||
|
var ids = new HashSet<Guid>(); |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
AddAssetIds(ids, schema, group); |
||||
|
} |
||||
|
|
||||
|
var assets = await GetAssetsAsync(context, ids); |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
ResolveAssetsUrls(schema, group, assets); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ResolveAssetsUrls(ISchemaEntity schema, IGrouping<Guid, ContentEntity> contents, ILookup<Guid, IEnrichedAssetEntity> assets) |
||||
|
{ |
||||
|
var temp = new HashSet<Guid>(); |
||||
|
|
||||
|
foreach (var field in schema.SchemaDef.ResolvingAssets()) |
||||
|
{ |
||||
|
foreach (var content in contents) |
||||
|
{ |
||||
|
if (content.ReferenceData == null) |
||||
|
{ |
||||
|
content.ReferenceData = new NamedContentData(); |
||||
|
} |
||||
|
|
||||
|
var fieldReference = content.ReferenceData.GetOrAdd(field.Name, _ => new ContentFieldData())!; |
||||
|
|
||||
|
if (content.DataDraft.TryGetValue(field.Name, out var fieldData) && fieldData != null) |
||||
|
{ |
||||
|
foreach (var (partitionKey, partitionValue) in fieldData) |
||||
|
{ |
||||
|
var referencedImage = |
||||
|
field.GetReferencedIds(partitionValue) |
||||
|
.Select(x => assets[x]) |
||||
|
.SelectMany(x => x) |
||||
|
.FirstOrDefault(x => x.Type == AssetType.Image); |
||||
|
|
||||
|
if (referencedImage != null) |
||||
|
{ |
||||
|
var url = assetUrlGenerator.GenerateUrl(referencedImage.Id.ToString()); |
||||
|
|
||||
|
content.CacheDependencies ??= new HashSet<object?>(); |
||||
|
|
||||
|
content.CacheDependencies.Add(referencedImage.Id); |
||||
|
content.CacheDependencies.Add(referencedImage.Version); |
||||
|
|
||||
|
fieldReference.AddJsonValue(partitionKey, JsonValue.Create(url)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<ILookup<Guid, IEnrichedAssetEntity>> GetAssetsAsync(Context context, HashSet<Guid> ids) |
||||
|
{ |
||||
|
if (ids.Count == 0) |
||||
|
{ |
||||
|
return EmptyAssets; |
||||
|
} |
||||
|
|
||||
|
var assets = await assetQuery.QueryAsync(context.Clone().WithoutAssetEnrichment(true), null, Q.Empty.WithIds(ids)); |
||||
|
|
||||
|
return assets.ToLookup(x => x.Id); |
||||
|
} |
||||
|
|
||||
|
private void AddAssetIds(HashSet<Guid> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents) |
||||
|
{ |
||||
|
foreach (var content in contents) |
||||
|
{ |
||||
|
content.DataDraft.AddReferencedIds(schema.SchemaDef.ResolvingAssets(), ids); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldEnrich(Context context) |
||||
|
{ |
||||
|
return context.IsFrontendClient && context.ShouldEnrichContent(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,167 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.ExtractReferenceIds; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps |
||||
|
{ |
||||
|
public sealed class ResolveReferences : IContentEnricherStep |
||||
|
{ |
||||
|
private static readonly ILookup<Guid, IEnrichedContentEntity> EmptyContents = Enumerable.Empty<IEnrichedContentEntity>().ToLookup(x => x.Id); |
||||
|
private readonly Lazy<IContentQueryService> contentQuery; |
||||
|
|
||||
|
private IContentQueryService ContentQuery |
||||
|
{ |
||||
|
get { return contentQuery.Value; } |
||||
|
} |
||||
|
|
||||
|
public ResolveReferences(Lazy<IContentQueryService> contentQuery) |
||||
|
{ |
||||
|
Guard.NotNull(contentQuery); |
||||
|
|
||||
|
this.contentQuery = contentQuery; |
||||
|
} |
||||
|
|
||||
|
public async Task EnrichAsync(Context context, IEnumerable<ContentEntity> contents, ProvideSchema schemas) |
||||
|
{ |
||||
|
if (ShouldEnrich(context)) |
||||
|
{ |
||||
|
var ids = new HashSet<Guid>(); |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
AddReferenceIds(ids, schema, group); |
||||
|
} |
||||
|
|
||||
|
var references = await GetReferencesAsync(context, ids); |
||||
|
|
||||
|
foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) |
||||
|
{ |
||||
|
var schema = await schemas(group.Key); |
||||
|
|
||||
|
await ResolveReferencesAsync(context, schema, group, references, schemas); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, IEnumerable<ContentEntity> contents, ILookup<Guid, IEnrichedContentEntity> references, ProvideSchema schemas) |
||||
|
{ |
||||
|
var formatted = new Dictionary<IContentEntity, JsonObject>(); |
||||
|
|
||||
|
foreach (var field in schema.SchemaDef.ResolvingReferences()) |
||||
|
{ |
||||
|
foreach (var content in contents) |
||||
|
{ |
||||
|
if (content.ReferenceData == null) |
||||
|
{ |
||||
|
content.ReferenceData = new NamedContentData(); |
||||
|
} |
||||
|
|
||||
|
var fieldReference = content.ReferenceData.GetOrAdd(field.Name, _ => new ContentFieldData())!; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (content.DataDraft.TryGetValue(field.Name, out var fieldData) && fieldData != null) |
||||
|
{ |
||||
|
foreach (var (partition, partitionValue) in fieldData) |
||||
|
{ |
||||
|
var referencedContents = |
||||
|
field.GetReferencedIds(partitionValue) |
||||
|
.Select(x => references[x]) |
||||
|
.SelectMany(x => x) |
||||
|
.ToList(); |
||||
|
|
||||
|
if (referencedContents.Count == 1) |
||||
|
{ |
||||
|
var reference = referencedContents[0]; |
||||
|
|
||||
|
var referencedSchema = await schemas(reference.SchemaId.Id); |
||||
|
|
||||
|
content.CacheDependencies ??= new HashSet<object?>(); |
||||
|
|
||||
|
content.CacheDependencies.Add(referencedSchema.Id); |
||||
|
content.CacheDependencies.Add(referencedSchema.Version); |
||||
|
content.CacheDependencies.Add(reference.Id); |
||||
|
content.CacheDependencies.Add(reference.Version); |
||||
|
|
||||
|
var value = formatted.GetOrAdd(reference, x => Format(x, context, referencedSchema)); |
||||
|
|
||||
|
fieldReference.AddJsonValue(partition, value); |
||||
|
} |
||||
|
else if (referencedContents.Count > 1) |
||||
|
{ |
||||
|
var value = CreateFallback(context, referencedContents); |
||||
|
|
||||
|
fieldReference.AddJsonValue(partition, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch (DomainObjectNotFoundException) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema) |
||||
|
{ |
||||
|
return content.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig); |
||||
|
} |
||||
|
|
||||
|
private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents) |
||||
|
{ |
||||
|
var text = $"{referencedContents.Count} Reference(s)"; |
||||
|
|
||||
|
var value = JsonValue.Object(); |
||||
|
|
||||
|
foreach (var partitionKey in context.App.LanguagesConfig.AllKeys) |
||||
|
{ |
||||
|
value.Add(partitionKey, text); |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
private void AddReferenceIds(HashSet<Guid> ids, ISchemaEntity schema, IEnumerable<ContentEntity> contents) |
||||
|
{ |
||||
|
foreach (var content in contents) |
||||
|
{ |
||||
|
content.DataDraft.AddReferencedIds(schema.SchemaDef.ResolvingReferences(), ids); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<ILookup<Guid, IEnrichedContentEntity>> GetReferencesAsync(Context context, HashSet<Guid> ids) |
||||
|
{ |
||||
|
if (ids.Count == 0) |
||||
|
{ |
||||
|
return EmptyContents; |
||||
|
} |
||||
|
|
||||
|
var references = await ContentQuery.QueryAsync(context.Clone().WithoutContentEnrichment(true), ids.ToList()); |
||||
|
|
||||
|
return references.ToLookup(x => x.Id); |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldEnrich(Context context) |
||||
|
{ |
||||
|
return context.IsFrontendClient && context.ShouldEnrichContent(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using FakeItEasy; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.TestHelpers |
||||
|
{ |
||||
|
public static class AExtensions |
||||
|
{ |
||||
|
public static T[] Is<T>(this INegatableArgumentConstraintManager<T[]> that, params T[]? values) |
||||
|
{ |
||||
|
return values == null ? that.IsNull() : that.IsSameSequenceAs(values); |
||||
|
} |
||||
|
|
||||
|
public static HashSet<T> Is<T>(this INegatableArgumentConstraintManager<HashSet<T>> that, IEnumerable<T>? values) |
||||
|
{ |
||||
|
return values == null ? that.IsNull() : that.Matches(x => x.Intersect(values).Count() == values.Count()); |
||||
|
} |
||||
|
|
||||
|
public static HashSet<T> Is<T>(this INegatableArgumentConstraintManager<HashSet<T>> that, params T[]? values) |
||||
|
{ |
||||
|
return values == null ? that.IsNull() : that.Matches(x => x.Intersect(values).Count() == values.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries |
||||
|
{ |
||||
|
public class ConvertDataTests |
||||
|
{ |
||||
|
private readonly ISchemaEntity schema; |
||||
|
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>(); |
||||
|
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); |
||||
|
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); |
||||
|
private readonly Context requestContext; |
||||
|
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
||||
|
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
||||
|
private readonly ProvideSchema schemaProvider; |
||||
|
private readonly ConvertData sut; |
||||
|
|
||||
|
public ConvertDataTests() |
||||
|
{ |
||||
|
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId)); |
||||
|
|
||||
|
var schemaDef = |
||||
|
new Schema("my-schema") |
||||
|
.AddReferences(1, "references", Partitioning.Invariant) |
||||
|
.AddAssets(2, "assets", Partitioning.Invariant) |
||||
|
.AddArray(3, "array", Partitioning.Invariant, a => a |
||||
|
.AddAssets(31, "nested")); |
||||
|
|
||||
|
schema = Mocks.Schema(appId, schemaId, schemaDef); |
||||
|
schemaProvider = x => Task.FromResult(schema); |
||||
|
|
||||
|
sut = new ConvertData(assetUrlGenerator, assetRepository, contentRepository); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_convert_data_only() |
||||
|
{ |
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider); |
||||
|
|
||||
|
Assert.NotNull(source.Data); |
||||
|
Assert.Null(source.DataDraft); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_convert_data_and_data_draft_when_frontend_user() |
||||
|
{ |
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
var ctx = new Context(Mocks.FrontendUser(), Mocks.App(appId)); |
||||
|
|
||||
|
await sut.EnrichAsync(ctx, Enumerable.Repeat(source, 1), schemaProvider); |
||||
|
|
||||
|
Assert.NotNull(source.Data); |
||||
|
Assert.NotNull(source.DataDraft); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_cleanup_references() |
||||
|
{ |
||||
|
var id1 = Guid.NewGuid(); |
||||
|
var id2 = Guid.NewGuid(); |
||||
|
|
||||
|
var source = |
||||
|
new NamedContentData() |
||||
|
.AddField("references", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(id1, id2))) |
||||
|
.AddField("assets", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(id1))) |
||||
|
.AddField("array", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue( |
||||
|
JsonValue.Array( |
||||
|
JsonValue.Object() |
||||
|
.Add("nested", JsonValue.Array(id1, id2))))); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("references", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array(id2))) |
||||
|
.AddField("assets", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue(JsonValue.Array())) |
||||
|
.AddField("array", |
||||
|
new ContentFieldData() |
||||
|
.AddJsonValue( |
||||
|
JsonValue.Array( |
||||
|
JsonValue.Object() |
||||
|
.Add("nested", JsonValue.Array(id2))))); |
||||
|
var content = PublishedContent(); |
||||
|
|
||||
|
content.Data = source; |
||||
|
content.DataDraft = source; |
||||
|
|
||||
|
A.CallTo(() => assetRepository.QueryIdsAsync(appId.Id, A<HashSet<Guid>>.That.Is(id1, id2))) |
||||
|
.Returns(new List<Guid> { id2 }); |
||||
|
|
||||
|
A.CallTo(() => contentRepository.QueryIdsAsync(appId.Id, A<HashSet<Guid>>.That.Is(id1, id2))) |
||||
|
.Returns(new List<(Guid, Guid)> { (id2, id2) }); |
||||
|
|
||||
|
var ctx = new Context(Mocks.FrontendUser(), Mocks.App(appId)); |
||||
|
|
||||
|
await sut.EnrichAsync(ctx, Enumerable.Repeat(content, 1), schemaProvider); |
||||
|
|
||||
|
Assert.Equal(expected, content.Data); |
||||
|
Assert.Equal(expected, content.DataDraft); |
||||
|
} |
||||
|
|
||||
|
private ContentEntity PublishedContent() |
||||
|
{ |
||||
|
return new ContentEntity |
||||
|
{ |
||||
|
Status = Status.Published, |
||||
|
Data = new NamedContentData(), |
||||
|
DataDraft = new NamedContentData(), |
||||
|
SchemaId = schemaId |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries |
||||
|
{ |
||||
|
public class EnrichForCachingTests |
||||
|
{ |
||||
|
private readonly ISchemaEntity schema; |
||||
|
private readonly Context requestContext; |
||||
|
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
||||
|
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
||||
|
private readonly ProvideSchema schemaProvider; |
||||
|
private readonly EnrichForCaching sut; |
||||
|
|
||||
|
public EnrichForCachingTests() |
||||
|
{ |
||||
|
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId)); |
||||
|
|
||||
|
schema = Mocks.Schema(appId, schemaId); |
||||
|
schemaProvider = x => Task.FromResult(schema); |
||||
|
|
||||
|
sut = new EnrichForCaching(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_app_version_and_schema_as_dependency() |
||||
|
{ |
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider); |
||||
|
|
||||
|
Assert.Contains(requestContext.App.Version, source.CacheDependencies); |
||||
|
|
||||
|
Assert.Contains(schema.Id, source.CacheDependencies); |
||||
|
Assert.Contains(schema.Version, source.CacheDependencies); |
||||
|
} |
||||
|
|
||||
|
private ContentEntity PublishedContent() |
||||
|
{ |
||||
|
return new ContentEntity { Status = Status.Published, SchemaId = schemaId }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries |
||||
|
{ |
||||
|
public class EnrichWithSchemaTests |
||||
|
{ |
||||
|
private readonly ISchemaEntity schema; |
||||
|
private readonly Context requestContext; |
||||
|
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
||||
|
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
||||
|
private readonly ProvideSchema schemaProvider; |
||||
|
private readonly EnrichWithSchema sut; |
||||
|
|
||||
|
public EnrichWithSchemaTests() |
||||
|
{ |
||||
|
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId)); |
||||
|
|
||||
|
schema = Mocks.Schema(appId, schemaId); |
||||
|
schemaProvider = x => Task.FromResult(schema); |
||||
|
|
||||
|
sut = new EnrichWithSchema(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_enrich_with_reference_fields() |
||||
|
{ |
||||
|
var ctx = new Context(Mocks.FrontendUser(), requestContext.App); |
||||
|
|
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
await sut.EnrichAsync(ctx, Enumerable.Repeat(source, 1), schemaProvider); |
||||
|
|
||||
|
Assert.NotNull(source.ReferenceFields); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_enrich_with_reference_fields_when_not_frontend() |
||||
|
{ |
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider); |
||||
|
|
||||
|
Assert.Null(source.ReferenceFields); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_enrich_with_schema_names() |
||||
|
{ |
||||
|
var ctx = new Context(Mocks.FrontendUser(), requestContext.App); |
||||
|
|
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
await sut.EnrichAsync(requestContext, Enumerable.Repeat(source, 1), schemaProvider); |
||||
|
|
||||
|
Assert.Equal("my-schema", source.SchemaName); |
||||
|
Assert.Equal("my-schema", source.SchemaDisplayName); |
||||
|
} |
||||
|
|
||||
|
private ContentEntity PublishedContent() |
||||
|
{ |
||||
|
return new ContentEntity { Status = Status.Published, SchemaId = schemaId }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Domain.Apps.Entities.Contents.Queries.Steps; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.Queries |
||||
|
{ |
||||
|
public class EnrichWithWorkflowsTests |
||||
|
{ |
||||
|
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); |
||||
|
private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); |
||||
|
private readonly ISchemaEntity schema; |
||||
|
private readonly Context requestContext; |
||||
|
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); |
||||
|
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); |
||||
|
private readonly ProvideSchema schemaProvider; |
||||
|
private readonly EnrichWithWorkflows sut; |
||||
|
|
||||
|
public EnrichWithWorkflowsTests() |
||||
|
{ |
||||
|
requestContext = new Context(Mocks.ApiUser(), Mocks.App(appId)); |
||||
|
|
||||
|
schema = Mocks.Schema(appId, schemaId); |
||||
|
schemaProvider = x => Task.FromResult(schema); |
||||
|
|
||||
|
A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A<Context>.Ignored, schemaId.Id.ToString())) |
||||
|
.Returns(schema); |
||||
|
|
||||
|
sut = new EnrichWithWorkflows(contentWorkflow); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_enrich_content_with_status_color() |
||||
|
{ |
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
A.CallTo(() => contentWorkflow.GetInfoAsync(source)) |
||||
|
.Returns(new StatusInfo(Status.Published, StatusColors.Published)); |
||||
|
|
||||
|
await sut.EnrichAsync(requestContext, new[] { source }, schemaProvider); |
||||
|
|
||||
|
Assert.Equal(StatusColors.Published, source.StatusColor); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_enrich_content_with_default_color_if_not_found() |
||||
|
{ |
||||
|
var source = PublishedContent(); |
||||
|
|
||||
|
A.CallTo(() => contentWorkflow.GetInfoAsync(source)) |
||||
|
.Returns(Task.FromResult<StatusInfo>(null!)); |
||||
|
|
||||
|
var ctx = requestContext.WithResolveFlow(true); |
||||
|
|
||||
|
await sut.EnrichAsync(ctx, new[] { source }, schemaProvider); |
||||
|
|
||||
|
Assert.Equal(StatusColors.Draft, source.StatusColor); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_enrich_content_with_can_update() |
||||
|
{ |
||||
|
var source = new ContentEntity { SchemaId = schemaId }; |
||||
|
|
||||
|
A.CallTo(() => contentWorkflow.CanUpdateAsync(source, requestContext.User)) |
||||
|
.Returns(true); |
||||
|
|
||||
|
var ctx = requestContext.WithResolveFlow(true); |
||||
|
|
||||
|
await sut.EnrichAsync(ctx, new[] { source }, schemaProvider); |
||||
|
|
||||
|
Assert.True(source.CanUpdate); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context() |
||||
|
{ |
||||
|
requestContext.WithResolveFlow(false); |
||||
|
|
||||
|
var source = new ContentEntity { SchemaId = schemaId }; |
||||
|
|
||||
|
var ctx = requestContext.WithResolveFlow(false); |
||||
|
|
||||
|
await sut.EnrichAsync(ctx, new[] { source }, schemaProvider); |
||||
|
|
||||
|
Assert.False(source.CanUpdate); |
||||
|
|
||||
|
A.CallTo(() => contentWorkflow.CanUpdateAsync(source, requestContext.User)) |
||||
|
.MustNotHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private ContentEntity PublishedContent() |
||||
|
{ |
||||
|
return new ContentEntity { Status = Status.Published, SchemaId = schemaId }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.ClientLibrary.Management; |
||||
|
using TestSuite.Fixtures; |
||||
|
using TestSuite.Model; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
||||
|
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
||||
|
|
||||
|
namespace TestSuite.ApiTests |
||||
|
{ |
||||
|
public class ContentCleanupTests : IClassFixture<ClientFixture> |
||||
|
{ |
||||
|
public ClientFixture _ { get; } |
||||
|
|
||||
|
public ContentCleanupTests(ClientFixture fixture) |
||||
|
{ |
||||
|
_ = fixture; |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_cleanup_old_data_from_update_response() |
||||
|
{ |
||||
|
var schemaName = $"schema-{DateTime.UtcNow.Ticks}"; |
||||
|
|
||||
|
// STEP 1: Create a schema.
|
||||
|
var schema = await _.Schemas.PostSchemaAsync(_.AppName, new CreateSchemaDto |
||||
|
{ |
||||
|
Name = schemaName, |
||||
|
Fields = new List<UpsertSchemaFieldDto> |
||||
|
{ |
||||
|
new UpsertSchemaFieldDto |
||||
|
{ |
||||
|
Name = "number", |
||||
|
Properties = new NumberFieldPropertiesDto |
||||
|
{ |
||||
|
IsRequired = true |
||||
|
} |
||||
|
}, |
||||
|
new UpsertSchemaFieldDto |
||||
|
{ |
||||
|
Name = "string", |
||||
|
Properties = new StringFieldPropertiesDto |
||||
|
{ |
||||
|
IsRequired = false |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
IsPublished = true |
||||
|
}); |
||||
|
|
||||
|
var contents = _.ClientManager.GetClient<TestEntity, TestEntityData>(schemaName); |
||||
|
|
||||
|
// STEP 2: Create a content for this schema.
|
||||
|
var data = new TestEntityData { Number = 12, String = "hello" }; |
||||
|
|
||||
|
var content_1 = await contents.CreateAsync(data); |
||||
|
|
||||
|
Assert.Equal(data.String, content_1.Data.String); |
||||
|
|
||||
|
|
||||
|
// STEP 3: Delete a field from schema.
|
||||
|
await _.Schemas.DeleteFieldAsync(_.AppName, schema.Name, schema.Fields.ElementAt(1).FieldId); |
||||
|
|
||||
|
|
||||
|
// STEP 4: Make any update.
|
||||
|
var content_2 = await contents.ChangeStatusAsync(content_1.Id, "Published"); |
||||
|
|
||||
|
// Should not return deleted field.
|
||||
|
Assert.Null(content_2.Data.String); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue