Browse Source

Validation performance improved and formatting fixes.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
8dee428bf2
  1. 37
      src/Squidex.Core/ContentValidator.cs
  2. 8
      src/Squidex.Core/FieldExtensions.cs
  3. 21
      src/Squidex.Core/Schemas/ValidationContext.cs
  4. 16
      src/Squidex.Core/Schemas/Validators/AssetsValidator.cs
  5. 16
      src/Squidex.Core/Schemas/Validators/ReferencesValidator.cs
  6. 14
      src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
  7. 17
      src/Squidex.Infrastructure.MongoDb/MongoEventConsumerInfoRepository.cs
  8. 4
      src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs
  9. 2
      src/Squidex.Infrastructure.MongoDb/UsageTracker/MongoUsageStore.cs
  10. 23
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs
  11. 33
      src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
  12. 25
      src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository.cs
  13. 22
      src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs
  14. 43
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
  15. 6
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  16. 11
      src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs
  17. 4
      src/Squidex.Read.MongoDb/Infrastructure/MongoPersistedGrantStore.cs
  18. 22
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  19. 8
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaWebhookRepository.cs
  20. 5
      src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj
  21. 4
      src/Squidex.Read/Assets/Repositories/IAssetRepository.cs
  22. 4
      src/Squidex.Read/Contents/Repositories/IContentRepository.cs
  23. 14
      src/Squidex.Write/Contents/ContentCommandHandler.cs
  24. 2
      src/Squidex/Config/Identity/IdentityUsage.cs
  25. 2
      src/Squidex/app/features/content/shared/references-editor.component.html
  26. 3
      tests/Squidex.Core.Tests/ContentValidationTests.cs
  27. 4
      tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs
  28. 6
      tests/Squidex.Core.Tests/Schemas/ReferencesFieldTests.cs
  29. 21
      tests/Squidex.Core.Tests/Schemas/ValidationTestExtensions.cs

37
src/Squidex.Core/ContentValidator.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -22,9 +23,9 @@ namespace Squidex.Core
private readonly Schema schema; private readonly Schema schema;
private readonly PartitionResolver partitionResolver; private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context; private readonly ValidationContext context;
private readonly List<ValidationError> errors = new List<ValidationError>(); private readonly ConcurrentBag<ValidationError> errors = new ConcurrentBag<ValidationError>();
public IReadOnlyList<ValidationError> Errors public IReadOnlyCollection<ValidationError> Errors
{ {
get { return errors; } get { return errors; }
} }
@ -39,10 +40,12 @@ namespace Squidex.Core
this.partitionResolver = partitionResolver; this.partitionResolver = partitionResolver;
} }
public async Task ValidatePartialAsync(NamedContentData data) public Task ValidatePartialAsync(NamedContentData data)
{ {
Guard.NotNull(data, nameof(data)); Guard.NotNull(data, nameof(data));
var tasks = new List<Task>();
foreach (var fieldData in data) foreach (var fieldData in data)
{ {
var fieldName = fieldData.Key; var fieldName = fieldData.Key;
@ -53,41 +56,51 @@ namespace Squidex.Core
} }
else else
{ {
await ValidateFieldPartialAsync(field, fieldData.Value); tasks.Add(ValidateFieldPartialAsync(field, fieldData.Value));
} }
} }
return Task.WhenAll(tasks);
} }
private async Task ValidateFieldPartialAsync(Field field, ContentFieldData fieldData) private Task ValidateFieldPartialAsync(Field field, ContentFieldData fieldData)
{ {
var partitioning = field.Paritioning; var partitioning = field.Paritioning;
var partition = partitionResolver(partitioning); var partition = partitionResolver(partitioning);
var tasks = new List<Task>();
foreach (var partitionValues in fieldData) foreach (var partitionValues in fieldData)
{ {
if (partition.TryGetItem(partitionValues.Key, out var item)) if (partition.TryGetItem(partitionValues.Key, out var item))
{ {
await field.ValidateAsync(partitionValues.Value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item)); tasks.Add(field.ValidateAsync(partitionValues.Value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item)));
} }
else else
{ {
errors.AddError($"<FIELD> has an unsupported {partitioning.Key} value '{partitionValues.Key}'", field); errors.AddError($"<FIELD> has an unsupported {partitioning.Key} value '{partitionValues.Key}'", field);
} }
} }
return Task.WhenAll(tasks);
} }
public async Task ValidateAsync(NamedContentData data) public Task ValidateAsync(NamedContentData data)
{ {
Guard.NotNull(data, nameof(data)); Guard.NotNull(data, nameof(data));
ValidateUnknownFields(data); ValidateUnknownFields(data);
var tasks = new List<Task>();
foreach (var field in schema.FieldsByName.Values) foreach (var field in schema.FieldsByName.Values)
{ {
var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData()); var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
await ValidateFieldAsync(field, fieldData); tasks.Add(ValidateFieldAsync(field, fieldData));
} }
return Task.WhenAll(tasks);
} }
private void ValidateUnknownFields(NamedContentData data) private void ValidateUnknownFields(NamedContentData data)
@ -101,11 +114,13 @@ namespace Squidex.Core
} }
} }
private async Task ValidateFieldAsync(Field field, ContentFieldData fieldData) private Task ValidateFieldAsync(Field field, ContentFieldData fieldData)
{ {
var partitioning = field.Paritioning; var partitioning = field.Paritioning;
var partition = partitionResolver(partitioning); var partition = partitionResolver(partitioning);
var tasks = new List<Task>();
foreach (var partitionValues in fieldData) foreach (var partitionValues in fieldData)
{ {
if (!partition.TryGetItem(partitionValues.Key, out var _)) if (!partition.TryGetItem(partitionValues.Key, out var _))
@ -118,8 +133,10 @@ namespace Squidex.Core
{ {
var value = fieldData.GetOrCreate(item.Key, k => JValue.CreateNull()); var value = fieldData.GetOrCreate(item.Key, k => JValue.CreateNull());
await field.ValidateAsync(value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item)); tasks.Add(field.ValidateAsync(value, context.Optional(item.IsOptional), m => errors.AddError(m, field, item)));
} }
return Task.WhenAll(tasks);
} }
} }
} }

8
src/Squidex.Core/FieldExtensions.cs

@ -7,7 +7,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
@ -18,17 +18,17 @@ namespace Squidex.Core
{ {
public static class FieldExtensions public static class FieldExtensions
{ {
public static void AddError(this ICollection<ValidationError> errors, string message, Field field, IFieldPartitionItem partitionItem = null) public static void AddError(this ConcurrentBag<ValidationError> errors, string message, Field field, IFieldPartitionItem partitionItem = null)
{ {
AddError(errors, message, !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name, field.Name, partitionItem); AddError(errors, message, !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name, field.Name, partitionItem);
} }
public static void AddError(this ICollection<ValidationError> errors, string message, string fieldName, IFieldPartitionItem partitionItem = null) public static void AddError(this ConcurrentBag<ValidationError> errors, string message, string fieldName, IFieldPartitionItem partitionItem = null)
{ {
AddError(errors, message, fieldName, fieldName, partitionItem); AddError(errors, message, fieldName, fieldName, partitionItem);
} }
public static void AddError(this ICollection<ValidationError> errors, string message, string displayName, string fieldName, IFieldPartitionItem partitionItem = null) public static void AddError(this ConcurrentBag<ValidationError> errors, string message, string displayName, string fieldName, IFieldPartitionItem partitionItem = null)
{ {
if (partitionItem != null && partitionItem != InvariantPartitioning.Instance.Master) if (partitionItem != null && partitionItem != InvariantPartitioning.Instance.Master)
{ {

21
src/Squidex.Core/Schemas/ValidationContext.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -14,22 +15,22 @@ namespace Squidex.Core.Schemas
{ {
public sealed class ValidationContext public sealed class ValidationContext
{ {
private readonly Func<Guid, Guid, Task<bool>> checkContent; private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent;
private readonly Func<Guid, Task<bool>> checkAsset; private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset;
public bool IsOptional { get; } public bool IsOptional { get; }
public ValidationContext( public ValidationContext(
Func<Guid, Guid, Task<bool>> checkContent, Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<Guid, Task<bool>> checkAsset) Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset)
: this(checkContent, checkAsset, false) : this(checkContent, checkAsset, false)
{ {
} }
private ValidationContext( private ValidationContext(
Func<Guid, Guid, Task<bool>> checkContent, Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<Guid, Task<bool>> checkAsset, Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset,
bool isOptional) bool isOptional)
{ {
Guard.NotNull(checkAsset, nameof(checkAsset)); Guard.NotNull(checkAsset, nameof(checkAsset));
@ -46,14 +47,14 @@ namespace Squidex.Core.Schemas
return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, isOptional); return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, isOptional);
} }
public async Task<bool> IsValidContentIdAsync(Guid schemaId, Guid contentId) public Task<IReadOnlyList<Guid>> GetInvalidContentIdsAsync(IEnumerable<Guid> contentIds, Guid schemaId)
{ {
return contentId != Guid.Empty && schemaId != Guid.Empty && await checkContent(schemaId, contentId); return checkContent(contentIds, schemaId);
} }
public async Task<bool> IsValidAssetIdAsync(Guid assetId) public Task<IReadOnlyList<Guid>> GetInvalidAssetIdsAsync(IEnumerable<Guid> assetId)
{ {
return assetId != Guid.Empty && await checkAsset(assetId); return checkAsset(assetId);
} }
} }
} }

16
src/Squidex.Core/Schemas/Validators/AssetsValidator.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Core.Schemas.Validators namespace Squidex.Core.Schemas.Validators
@ -35,21 +34,12 @@ namespace Squidex.Core.Schemas.Validators
return; return;
} }
var assetTasks = assets.AssetIds.Select(x => CheckAssetAsync(context, x)).ToArray(); var invalidIds = await context.GetInvalidAssetIdsAsync(assets.AssetIds);
await Task.WhenAll(assetTasks); foreach (var invalidId in invalidIds)
foreach (var notFoundId in assetTasks.Where(x => !x.Result.IsFound).Select(x => x.Result.AssetId))
{ {
addError($"<FIELD> contains invalid asset '{notFoundId}'"); addError($"<FIELD> contains invalid asset '{invalidId}'");
}
} }
private static async Task<(Guid AssetId, bool IsFound)> CheckAssetAsync(ValidationContext context, Guid id)
{
var isFound = await context.IsValidAssetIdAsync(id);
return (id, isFound);
} }
} }
} }

16
src/Squidex.Core/Schemas/Validators/ReferencesValidator.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Core.Schemas.Validators namespace Squidex.Core.Schemas.Validators
@ -37,21 +36,12 @@ namespace Squidex.Core.Schemas.Validators
return; return;
} }
var referenceTasks = references.ContentIds.Select(x => CheckReferenceAsync(context, x)).ToArray(); var invalidIds = await context.GetInvalidContentIdsAsync(references.ContentIds, schemaId);
await Task.WhenAll(referenceTasks); foreach (var invalidId in invalidIds)
foreach (var notFoundId in referenceTasks.Where(x => !x.Result.IsFound).Select(x => x.Result.ReferenceId))
{ {
addError($"<FIELD> contains invalid reference '{notFoundId}'"); addError($"<FIELD> contains invalid reference '{invalidId}'");
}
} }
private async Task<(Guid ReferenceId, bool IsFound)> CheckReferenceAsync(ValidationContext context, Guid id)
{
var isFound = await context.IsValidContentIdAsync(schemaId, id);
return (id, isFound);
} }
} }
} }

14
src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs

@ -57,10 +57,10 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{ {
var indexNames = var indexNames =
await Task.WhenAll( await Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.EventsOffset), new CreateIndexOptions { Unique = true }), collection.Indexes.CreateOneAsync(Index.Ascending(x => x.EventsOffset), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.EventStreamOffset).Ascending(x => x.EventStream), new CreateIndexOptions { Unique = true }), collection.Indexes.CreateOneAsync(Index.Ascending(x => x.EventStreamOffset).Ascending(x => x.EventStream), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(IndexKeys.Descending(x => x.EventsOffset), new CreateIndexOptions { Unique = true }), collection.Indexes.CreateOneAsync(Index.Descending(x => x.EventsOffset), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(IndexKeys.Descending(x => x.EventStreamOffset).Ascending(x => x.EventStream), new CreateIndexOptions { Unique = true })); collection.Indexes.CreateOneAsync(Index.Descending(x => x.EventStreamOffset).Ascending(x => x.EventStream), new CreateIndexOptions { Unique = true }));
eventsOffsetIndex = indexNames[0]; eventsOffsetIndex = indexNames[0];
} }
@ -201,7 +201,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{ {
var document = var document =
await Collection.Find(x => x.EventsOffset <= startEventNumber) await Collection.Find(x => x.EventsOffset <= startEventNumber)
.Project<BsonDocument>(Projection .Project<BsonDocument>(Project
.Include(x => x.EventsOffset)) .Include(x => x.EventsOffset))
.SortByDescending(x => x.EventsOffset).Limit(1) .SortByDescending(x => x.EventsOffset).Limit(1)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
@ -218,7 +218,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{ {
var document = var document =
await Collection.Find(new BsonDocument()) await Collection.Find(new BsonDocument())
.Project<BsonDocument>(Projection .Project<BsonDocument>(Project
.Include(x => x.EventsOffset) .Include(x => x.EventsOffset)
.Include(x => x.EventsCount)) .Include(x => x.EventsCount))
.SortByDescending(x => x.EventsOffset).Limit(1) .SortByDescending(x => x.EventsOffset).Limit(1)
@ -236,7 +236,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{ {
var document = var document =
await Collection.Find(x => x.EventStream == streamName) await Collection.Find(x => x.EventStream == streamName)
.Project<BsonDocument>(Projection .Project<BsonDocument>(Project
.Include(x => x.EventStreamOffset) .Include(x => x.EventStreamOffset)
.Include(x => x.EventsCount)) .Include(x => x.EventsCount))
.SortByDescending(x => x.EventsOffset).Limit(1) .SortByDescending(x => x.EventsOffset).Limit(1)

17
src/Squidex.Infrastructure.MongoDb/MongoEventConsumerInfoRepository.cs

@ -61,22 +61,31 @@ namespace Squidex.Infrastructure.MongoDb
public Task StartAsync(string consumerName) public Task StartAsync(string consumerName)
{ {
return Collection.UpdateOneAsync(x => x.Name == consumerName, Update.Unset(x => x.IsStopped)); return Collection.UpdateOneAsync(x => x.Name == consumerName,
Update.Unset(x => x.IsStopped));
} }
public Task StopAsync(string consumerName, string error = null) public Task StopAsync(string consumerName, string error = null)
{ {
return Collection.UpdateOneAsync(x => x.Name == consumerName, Update.Set(x => x.IsStopped, true).Set(x => x.Error, error)); return Collection.UpdateOneAsync(x => x.Name == consumerName,
Update.Set(x => x.IsStopped, true).Set(x => x.Error, error));
} }
public Task ResetAsync(string consumerName) public Task ResetAsync(string consumerName)
{ {
return Collection.UpdateOneAsync(x => x.Name == consumerName, Update.Set(x => x.IsResetting, true)); return Collection.UpdateOneAsync(x => x.Name == consumerName,
Update.Set(x => x.IsResetting, true));
} }
public Task SetLastHandledEventNumberAsync(string consumerName, long eventNumber) public Task SetLastHandledEventNumberAsync(string consumerName, long eventNumber)
{ {
return Collection.ReplaceOneAsync(x => x.Name == consumerName, new MongoEventConsumerInfo { Name = consumerName, LastHandledEventNumber = eventNumber }); return Collection.ReplaceOneAsync(x => x.Name == consumerName,
CreateEntity(consumerName, eventNumber));
}
private static MongoEventConsumerInfo CreateEntity(string consumerName, long eventNumber)
{
return new MongoEventConsumerInfo { Name = consumerName, LastHandledEventNumber = eventNumber };
} }
} }
} }

4
src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs

@ -30,7 +30,7 @@ namespace Squidex.Infrastructure.MongoDb
} }
} }
protected static ProjectionDefinitionBuilder<TEntity> Projection protected static ProjectionDefinitionBuilder<TEntity> Project
{ {
get get
{ {
@ -62,7 +62,7 @@ namespace Squidex.Infrastructure.MongoDb
} }
} }
protected static IndexKeysDefinitionBuilder<TEntity> IndexKeys protected static IndexKeysDefinitionBuilder<TEntity> Index
{ {
get get
{ {

2
src/Squidex.Infrastructure.MongoDb/UsageTracker/MongoUsageStore.cs

@ -31,7 +31,7 @@ namespace Squidex.Infrastructure.MongoDb.UsageTracker
protected override Task SetupCollectionAsync(IMongoCollection<MongoUsage> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoUsage> collection)
{ {
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Key).Ascending(x => x.Date)); return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Key).Ascending(x => x.Date));
} }
public Task TrackUsagesAsync(DateTime date, string key, double count, double elapsedMs) public Task TrackUsagesAsync(DateTime date, string key, double count, double elapsedMs)

23
src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs

@ -31,7 +31,7 @@ namespace Squidex.Read.MongoDb.Apps
protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{ {
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name)); return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
} }
protected override MongoCollectionSettings CollectionSettings() protected override MongoCollectionSettings CollectionSettings()
@ -41,26 +41,29 @@ namespace Squidex.Read.MongoDb.Apps
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId) public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId)
{ {
var entities = var appEntities =
await Collection.Find(s => s.Contributors.ContainsKey(subjectId)).ToListAsync(); await Collection.Find(s => s.Contributors.ContainsKey(subjectId))
.ToListAsync();
return entities; return appEntities;
} }
public async Task<IAppEntity> FindAppAsync(Guid id) public async Task<IAppEntity> FindAppAsync(Guid id)
{ {
var entity = var appEntity =
await Collection.Find(s => s.Id == id).FirstOrDefaultAsync(); await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return entity; return appEntity;
} }
public async Task<IAppEntity> FindAppAsync(string name) public async Task<IAppEntity> FindAppAsync(string name)
{ {
var entity = var appEntity =
await Collection.Find(s => s.Name == name).FirstOrDefaultAsync(); await Collection.Find(s => s.Name == name)
.FirstOrDefaultAsync();
return entity; return appEntity;
} }
} }
} }

33
src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs

@ -17,6 +17,8 @@ using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Assets; using Squidex.Read.Assets;
using Squidex.Read.Assets.Repositories; using Squidex.Read.Assets.Repositories;
// ReSharper disable ClassNeverInstantiated.Local
namespace Squidex.Read.MongoDb.Assets namespace Squidex.Read.MongoDb.Assets
{ {
public partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, IEventConsumer public partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, IEventConsumer
@ -33,40 +35,47 @@ namespace Squidex.Read.MongoDb.Assets
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection)
{ {
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.AppId).Ascending(x => x.IsDeleted).Descending(x => x.LastModified).Ascending(x => x.FileName).Ascending(x => x.MimeType)); return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.IsDeleted).Descending(x => x.LastModified).Ascending(x => x.FileName).Ascending(x => x.MimeType));
} }
public async Task<bool> ExistsAsync(Guid appId, Guid assetId) public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, IList<Guid> assetIds)
{ {
return await Collection.Find(x => x.Id == assetId && x.AppId == appId).CountAsync() == 1; var assetEntities =
await Collection.Find(x => assetIds.Contains(x.Id) && x.AppId == appId).Project<BsonDocument>(Project.Include(x => x.Id))
.ToListAsync();
return assetIds.Except(assetEntities.Select(x => x["Id"].AsGuid)).ToList();
} }
public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0) public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0)
{ {
var filter = CreateFilter(appId, mimeTypes, ids, query); var filter = CreateFilter(appId, mimeTypes, ids, query);
var assets = var assetEntities =
await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified).ToListAsync(); await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified)
.ToListAsync();
return assets.OfType<IAssetEntity>().ToList(); return assetEntities.OfType<IAssetEntity>().ToList();
} }
public async Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null) public async Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null)
{ {
var filter = CreateFilter(appId, mimeTypes, ids, query); var filter = CreateFilter(appId, mimeTypes, ids, query);
var count = var assetsCount =
await Collection.Find(filter).CountAsync(); await Collection.Find(filter)
.CountAsync();
return count; return assetsCount;
} }
public async Task<IAssetEntity> FindAssetAsync(Guid id) public async Task<IAssetEntity> FindAssetAsync(Guid id)
{ {
var entity = var assetEntity =
await Collection.Find(s => s.Id == id).FirstOrDefaultAsync(); await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return entity; return assetEntity;
} }
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query) private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query)

25
src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository.cs

@ -34,16 +34,19 @@ namespace Squidex.Read.MongoDb.Assets
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
{ {
return Task.WhenAll( return Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.AppId).Ascending(x => x.Date)), collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.Date)),
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.AppId).Descending(x => x.Date))); collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Date)));
} }
public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate) public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate)
{ {
var originalSizes = await Collection.Find(x => x.AppId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date).ToListAsync(); var originalSizesEntities =
await Collection.Find(x => x.AppId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date)
.ToListAsync();
var enrichedSizes = new List<MongoAssetStatsEntity>(); var enrichedSizes = new List<MongoAssetStatsEntity>();
var sizesDictionary = originalSizes.ToDictionary(x => x.Date); var sizesDictionary = originalSizesEntities.ToDictionary(x => x.Date);
var previousSize = long.MinValue; var previousSize = long.MinValue;
var previousCount = long.MinValue; var previousCount = long.MinValue;
@ -61,10 +64,12 @@ namespace Squidex.Read.MongoDb.Assets
{ {
if (previousSize < 0) if (previousSize < 0)
{ {
var firstBeforeRange = await Collection.Find(x => x.AppId == appId && x.Date < fromDate).SortByDescending(x => x.Date).FirstOrDefaultAsync(); var firstBeforeRangeEntity =
await Collection.Find(x => x.AppId == appId && x.Date < fromDate).SortByDescending(x => x.Date)
.FirstOrDefaultAsync();
previousSize = firstBeforeRange?.TotalSize ?? 0L; previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L;
previousCount = firstBeforeRange?.TotalCount ?? 0L; previousCount = firstBeforeRangeEntity?.TotalCount ?? 0L;
} }
size = new MongoAssetStatsEntity size = new MongoAssetStatsEntity
@ -83,9 +88,11 @@ namespace Squidex.Read.MongoDb.Assets
public async Task<long> GetTotalSizeAsync(Guid appId) public async Task<long> GetTotalSizeAsync(Guid appId)
{ {
var entity = await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date).FirstOrDefaultAsync(); var totalSizeEntity =
await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date)
.FirstOrDefaultAsync();
return entity?.TotalSize ?? 0; return totalSizeEntity?.TotalSize ?? 0;
} }
} }
} }

22
src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs

@ -53,26 +53,30 @@ namespace Squidex.Read.MongoDb.Assets
{ {
var id = $"{appId}_{date:yyyy-MM-dd}"; var id = $"{appId}_{date:yyyy-MM-dd}";
var entity = await Collection.Find(x => x.Id == id).FirstOrDefaultAsync(); var assetStatsEntity =
await Collection.Find(x => x.Id == id)
.FirstOrDefaultAsync();
if (entity == null) if (assetStatsEntity == null)
{ {
var last = await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date).FirstOrDefaultAsync(); var lastEntity =
await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date)
.FirstOrDefaultAsync();
entity = new MongoAssetStatsEntity assetStatsEntity = new MongoAssetStatsEntity
{ {
Id = id, Id = id,
Date = date, Date = date,
AppId = appId, AppId = appId,
TotalSize = last?.TotalSize ?? 0, TotalSize = lastEntity?.TotalSize ?? 0,
TotalCount = last?.TotalCount ?? 0 TotalCount = lastEntity?.TotalCount ?? 0
}; };
} }
entity.TotalSize += size; assetStatsEntity.TotalSize += size;
entity.TotalCount += count; assetStatsEntity.TotalCount += count;
await Collection.ReplaceOneAsync(x => x.Id == id, entity, Upsert); await Collection.ReplaceOneAsync(x => x.Id == id, assetStatsEntity, Upsert);
} }
} }
} }

43
src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs

@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.OData.Core; using Microsoft.OData.Core;
using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
@ -47,7 +48,15 @@ namespace Squidex.Read.MongoDb.Contents
} }
} }
protected static IndexKeysDefinitionBuilder<MongoContentEntity> IndexKeys protected static ProjectionDefinitionBuilder<MongoContentEntity> Projection
{
get
{
return Builders<MongoContentEntity>.Projection;
}
}
protected static IndexKeysDefinitionBuilder<MongoContentEntity> Index
{ {
get get
{ {
@ -68,7 +77,7 @@ namespace Squidex.Read.MongoDb.Contents
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity) public async Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity)
{ {
List<IContentEntity> result = null; var contentEntities = (List<IContentEntity>)null;
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) => await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{ {
@ -106,15 +115,15 @@ namespace Squidex.Read.MongoDb.Contents
entity.ParseData(schemaEntity.Schema); entity.ParseData(schemaEntity.Schema);
} }
result = entities.OfType<IContentEntity>().ToList(); contentEntities = entities.OfType<IContentEntity>().ToList();
}); });
return result; return contentEntities;
} }
public async Task<long> CountAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity) public async Task<long> CountAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity)
{ {
var result = 0L; var contentsCount = 0L;
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) => await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{ {
@ -140,36 +149,40 @@ namespace Squidex.Read.MongoDb.Contents
throw new ValidationException("Failed to parse query: " + ex.Message, ex); throw new ValidationException("Failed to parse query: " + ex.Message, ex);
} }
result = await cursor.CountAsync(); contentsCount = await cursor.CountAsync();
}); });
return result; return contentsCount;
} }
public async Task<bool> ExistsAsync(Guid appId, Guid schemaId, Guid contentId) public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds)
{ {
var result = false; var contentEntities = (List<BsonDocument>)null;
await ForAppIdAsync(appId, async collection => await ForAppIdAsync(appId, async collection =>
{ {
result = await collection.Find(x => x.Id == contentId && x.SchemaId == schemaId).CountAsync() == 1; contentEntities =
await collection.Find(x => contentIds.Contains(x.Id) && x.AppId == appId).Project<BsonDocument>(Projection.Include(x => x.Id))
.ToListAsync();
}); });
return result; return contentIds.Except(contentEntities.Select(x => x["Id"].AsGuid)).ToList();
} }
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity) public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity)
{ {
MongoContentEntity result = null; var contentEntity = (MongoContentEntity)null;
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) => await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{ {
result = await collection.Find(x => x.Id == id).FirstOrDefaultAsync(); contentEntity =
await collection.Find(x => x.Id == id)
.FirstOrDefaultAsync();
result?.ParseData(schemaEntity.Schema); contentEntity?.ParseData(schemaEntity.Schema);
}); });
return result; return contentEntity;
} }
private async Task ForSchemaAsync(Guid appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action) private async Task ForSchemaAsync(Guid appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action)

6
src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -62,9 +62,9 @@ namespace Squidex.Read.MongoDb.Contents
{ {
return ForAppIdAsync(@event.AppId.Id, async collection => return ForAppIdAsync(@event.AppId.Id, async collection =>
{ {
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.ReferencedIds)); await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished)); await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsPublished));
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.DataText)); await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText));
}); });
} }

11
src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs

@ -60,21 +60,22 @@ namespace Squidex.Read.MongoDb.History
{ {
return Task.WhenAll( return Task.WhenAll(
collection.Indexes.CreateOneAsync( collection.Indexes.CreateOneAsync(
IndexKeys Index
.Ascending(x => x.AppId) .Ascending(x => x.AppId)
.Ascending(x => x.Channel) .Ascending(x => x.Channel)
.Descending(x => x.Created) .Descending(x => x.Created)
.Descending(x => x.SessionEventIndex)), .Descending(x => x.SessionEventIndex)),
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) })); collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) }));
} }
public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count) public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count)
{ {
var entities = var historyEventEntities =
await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix) await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix)
.SortByDescending(x => x.Created).ThenByDescending(x => x.SessionEventIndex).Limit(count).ToListAsync(); .SortByDescending(x => x.Created).ThenByDescending(x => x.SessionEventIndex).Limit(count)
.ToListAsync();
return entities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList(); return historyEventEntities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList();
} }
public async Task On(Envelope<IEvent> @event) public async Task On(Envelope<IEvent> @event)

4
src/Squidex.Read.MongoDb/Infrastructure/MongoPersistedGrantStore.cs

@ -40,8 +40,8 @@ namespace Squidex.Read.MongoDb.Infrastructure
protected override Task SetupCollectionAsync(IMongoCollection<PersistedGrant> collection) protected override Task SetupCollectionAsync(IMongoCollection<PersistedGrant> collection)
{ {
return Task.WhenAll( return Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.ClientId)), collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ClientId)),
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SubjectId))); collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SubjectId)));
} }
public Task StoreAsync(PersistedGrant grant) public Task StoreAsync(PersistedGrant grant)

22
src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs

@ -44,38 +44,40 @@ namespace Squidex.Read.MongoDb.Schemas
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection)
{ {
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name)); return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
} }
public async Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId) public async Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId)
{ {
var entities = await Collection.Find(s => s.AppId == appId && !s.IsDeleted).ToListAsync(); var schemaEntities =
await Collection.Find(s => s.AppId == appId && !s.IsDeleted)
.ToListAsync();
entities.ForEach(x => x.DeserializeSchema(serializer)); schemaEntities.ForEach(x => x.DeserializeSchema(serializer));
return entities.OfType<ISchemaEntity>().ToList(); return schemaEntities.OfType<ISchemaEntity>().ToList();
} }
public async Task<ISchemaEntity> FindSchemaAsync(Guid appId, string name) public async Task<ISchemaEntity> FindSchemaAsync(Guid appId, string name)
{ {
var entity = var schemaEntity =
await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted) await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
entity?.DeserializeSchema(serializer); schemaEntity?.DeserializeSchema(serializer);
return entity; return schemaEntity;
} }
public async Task<ISchemaEntity> FindSchemaAsync(Guid schemaId) public async Task<ISchemaEntity> FindSchemaAsync(Guid schemaId)
{ {
var entity = var schemaEntity =
await Collection.Find(s => s.Id == schemaId) await Collection.Find(s => s.Id == schemaId)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
entity?.DeserializeSchema(serializer); schemaEntity?.DeserializeSchema(serializer);
return entity; return schemaEntity;
} }
} }
} }

8
src/Squidex.Read.MongoDb/Schemas/MongoSchemaWebhookRepository.cs

@ -52,12 +52,12 @@ namespace Squidex.Read.MongoDb.Schemas
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaWebhookEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaWebhookEntity> collection)
{ {
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SchemaId)); return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaId));
} }
public async Task<IReadOnlyList<ISchemaWebhookEntity>> QueryByAppAsync(Guid appId) public async Task<IReadOnlyList<ISchemaWebhookEntity>> QueryByAppAsync(Guid appId)
{ {
return await Collection.Find(x => x.AppId == appId).ToListAsync(); return await Collection.Find(Filter.Eq(x => x.AppId, appId)).ToListAsync();
} }
public async Task<IReadOnlyList<ISchemaWebhookUrlEntity>> QueryUrlsBySchemaAsync(Guid appId, Guid schemaId) public async Task<IReadOnlyList<ISchemaWebhookUrlEntity>> QueryUrlsBySchemaAsync(Guid appId, Guid schemaId)
@ -69,7 +69,9 @@ namespace Squidex.Read.MongoDb.Schemas
public async Task AddInvokationAsync(Guid webhookId, string dump, WebhookResult result, TimeSpan elapsed) public async Task AddInvokationAsync(Guid webhookId, string dump, WebhookResult result, TimeSpan elapsed)
{ {
var webhookEntity = await Collection.Find(x => x.Id == webhookId).FirstOrDefaultAsync(); var webhookEntity =
await Collection.Find(x => x.Id == webhookId)
.FirstOrDefaultAsync();
if (webhookEntity != null) if (webhookEntity != null)
{ {

5
src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj

@ -23,4 +23,9 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="Microsoft.OData.Core" Version="6.15.0" /> <PackageReference Include="Microsoft.OData.Core" Version="6.15.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="System.Reflection.Metadata">
<HintPath>C:\Users\mail2\.nuget\packages\system.reflection.metadata\1.4.1\lib\netstandard1.1\System.Reflection.Metadata.dll</HintPath>
</Reference>
</ItemGroup>
</Project> </Project>

4
src/Squidex.Read/Assets/Repositories/IAssetRepository.cs

@ -16,9 +16,9 @@ namespace Squidex.Read.Assets.Repositories
{ {
Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0); Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0);
Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null); Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, IList<Guid> assetIds);
Task<bool> ExistsAsync(Guid appId, Guid assetId); Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null);
Task<IAssetEntity> FindAssetAsync(Guid id); Task<IAssetEntity> FindAssetAsync(Guid id);
} }

4
src/Squidex.Read/Contents/Repositories/IContentRepository.cs

@ -17,9 +17,9 @@ namespace Squidex.Read.Contents.Repositories
{ {
Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity); Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity);
Task<long> CountAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity); Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds);
Task<bool> ExistsAsync(Guid appId, Guid schemaId, Guid contentId); Task<long> CountAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity);
Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity); Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity);
} }

14
src/Squidex.Write/Contents/ContentCommandHandler.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Core; using Squidex.Core;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
@ -21,6 +22,8 @@ using Squidex.Read.Contents.Repositories;
using Squidex.Read.Schemas.Services; using Squidex.Read.Schemas.Services;
using Squidex.Write.Contents.Commands; using Squidex.Write.Contents.Commands;
// ReSharper disable ConvertToLambdaExpression
namespace Squidex.Write.Contents namespace Squidex.Write.Contents
{ {
public class ContentCommandHandler : ICommandHandler public class ContentCommandHandler : ICommandHandler
@ -112,7 +115,16 @@ namespace Squidex.Write.Contents
var appId = command.AppId.Id; var appId = command.AppId.Id;
var validationContext = new ValidationContext((x, y) => contentRepository.ExistsAsync(appId, x, y), x => assetRepository.ExistsAsync(appId, x)); var validationContext =
new ValidationContext(
(contentIds, schemaId) =>
{
return contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList());
},
assetIds =>
{
return assetRepository.QueryNotFoundAsync(appId, assetIds.ToList());
});
await command.Data.ValidateAsync(validationContext, schemaObject, taskForApp.Result.PartitionResolver, schemaErrors); await command.Data.ValidateAsync(validationContext, schemaObject, taskForApp.Result.PartitionResolver, schemaErrors);

2
src/Squidex/Config/Identity/IdentityUsage.cs

@ -64,7 +64,7 @@ namespace Squidex.Config.Identity
async Task userInitAsync(IUser theUser) async Task userInitAsync(IUser theUser)
{ {
await userManager.RemovePasswordAsync(theUser); await userManager.RemovePasswordAsync(theUser);
await userManager.ChangePasswordAsync(theUser, null, adminEmail); await userManager.ChangePasswordAsync(theUser, null, adminPass);
await userManager.AddToRoleAsync(theUser, SquidexRoles.Administrator); await userManager.AddToRoleAsync(theUser, SquidexRoles.Administrator);
} }

2
src/Squidex/app/features/content/shared/references-editor.component.html

@ -1,7 +1,7 @@
<div class="references-container" [class.disabled]="isDisabled"> <div class="references-container" [class.disabled]="isDisabled">
<div class="drop-area-container" *ngIf="schema && !isDisabled"> <div class="drop-area-container" *ngIf="schema && !isDisabled">
<div class="drop-area" dnd-droppable (onDropSuccess)="onContentDropped($event.dragData.content)" [allowDrop]="canDrop()" [routerLink]="['references', schemaId, languageCode]"> <div class="drop-area" dnd-droppable (onDropSuccess)="onContentDropped($event.dragData.content)" [allowDrop]="canDrop()" [routerLink]="['references', schemaId, languageCode]">
Drop content here to add them. Drop content here to add a reference.
</div> </div>
</div> </div>

3
tests/Squidex.Core.Tests/ContentValidationTests.cs

@ -12,7 +12,6 @@ using FluentAssertions;
using Squidex.Core.Contents; using Squidex.Core.Contents;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
using Xunit; using Xunit;
namespace Squidex.Core namespace Squidex.Core
@ -21,7 +20,7 @@ namespace Squidex.Core
{ {
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.DE, Language.EN); private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.DE, Language.EN);
private readonly List<ValidationError> errors = new List<ValidationError>(); private readonly List<ValidationError> errors = new List<ValidationError>();
private readonly ValidationContext context = new ValidationContext((x, y) => TaskHelper.True, x => TaskHelper.True); private readonly ValidationContext context = ValidationTestExtensions.ValidContext;
private Schema schema = Schema.Create("my-name", new SchemaProperties()); private Schema schema = Schema.Create("my-name", new SchemaProperties());
[Fact] [Fact]

4
tests/Squidex.Core.Tests/Schemas/AssetsFieldTests.cs

@ -12,7 +12,6 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Tasks;
using Xunit; using Xunit;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
@ -20,7 +19,6 @@ namespace Squidex.Core.Schemas
public class AssetsFieldTests public class AssetsFieldTests
{ {
private readonly List<string> errors = new List<string>(); private readonly List<string> errors = new List<string>();
private static readonly ValidationContext InvalidAssetContext = new ValidationContext((x, y) => TaskHelper.False, x => TaskHelper.False);
[Fact] [Fact]
public void Should_instantiate_field() public void Should_instantiate_field()
@ -100,7 +98,7 @@ namespace Squidex.Core.Schemas
var sut = new AssetsField(1, "my-asset", Partitioning.Invariant); var sut = new AssetsField(1, "my-asset", Partitioning.Invariant);
await sut.ValidateAsync(CreateValue(assetId), errors, InvalidAssetContext); await sut.ValidateAsync(CreateValue(assetId), errors, ValidationTestExtensions.InvalidContext(assetId));
errors.ShouldBeEquivalentTo( errors.ShouldBeEquivalentTo(
new[] { $"<FIELD> contains invalid asset '{assetId}'" }); new[] { $"<FIELD> contains invalid asset '{assetId}'" });

6
tests/Squidex.Core.Tests/Schemas/ReferencesFieldTests.cs

@ -12,7 +12,6 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Tasks;
using Xunit; using Xunit;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
@ -21,7 +20,6 @@ namespace Squidex.Core.Schemas
{ {
private readonly List<string> errors = new List<string>(); private readonly List<string> errors = new List<string>();
private readonly Guid schemaId = Guid.NewGuid(); private readonly Guid schemaId = Guid.NewGuid();
private static readonly ValidationContext InvalidSchemaContext = new ValidationContext((x, y) => TaskHelper.False, x => TaskHelper.False);
[Fact] [Fact]
public void Should_instantiate_field() public void Should_instantiate_field()
@ -46,7 +44,7 @@ namespace Squidex.Core.Schemas
var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant); var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant);
await sut.ValidateAsync(CreateValue(referenceId), errors, InvalidSchemaContext); await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.ValidContext);
Assert.Empty(errors); Assert.Empty(errors);
} }
@ -101,7 +99,7 @@ namespace Squidex.Core.Schemas
var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId }); var sut = new ReferencesField(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId });
await sut.ValidateAsync(CreateValue(referenceId), errors, InvalidSchemaContext); await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.InvalidContext(referenceId));
errors.ShouldBeEquivalentTo( errors.ShouldBeEquivalentTo(
new[] { $"<FIELD> contains invalid reference '{referenceId}'" }); new[] { $"<FIELD> contains invalid reference '{referenceId}'" });

21
tests/Squidex.Core.Tests/Schemas/ValidationTestExtensions.cs

@ -6,36 +6,45 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas.Validators; using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
public static class ValidationTestExtensions public static class ValidationTestExtensions
{ {
private static readonly ValidationContext EverythingValidContext = new ValidationContext((x, y) => TaskHelper.True, x => TaskHelper.True); private static readonly Task<IReadOnlyList<Guid>> ValidIds = Task.FromResult<IReadOnlyList<Guid>>(new List<Guid>());
public static readonly ValidationContext ValidContext = new ValidationContext((x, y) => ValidIds, x => ValidIds);
public static Task ValidateAsync(this IValidator validator, object value, IList<string> errors, ValidationContext context = null) public static Task ValidateAsync(this IValidator validator, object value, IList<string> errors, ValidationContext context = null)
{ {
return validator.ValidateAsync(value, context ?? EverythingValidContext, errors.Add); return validator.ValidateAsync(value, context ?? ValidContext, errors.Add);
} }
public static Task ValidateOptionalAsync(this IValidator validator, object value, IList<string> errors, ValidationContext context = null) public static Task ValidateOptionalAsync(this IValidator validator, object value, IList<string> errors, ValidationContext context = null)
{ {
return validator.ValidateAsync(value, (context ?? EverythingValidContext).Optional(true), errors.Add); return validator.ValidateAsync(value, (context ?? ValidContext).Optional(true), errors.Add);
} }
public static Task ValidateAsync(this Field field, JToken value, IList<string> errors, ValidationContext context = null) public static Task ValidateAsync(this Field field, JToken value, IList<string> errors, ValidationContext context = null)
{ {
return field.ValidateAsync(value, context ?? EverythingValidContext, errors.Add); return field.ValidateAsync(value, context ?? ValidContext, errors.Add);
} }
public static Task ValidateOptionalAsync(this Field field, JToken value, IList<string> errors, ValidationContext context = null) public static Task ValidateOptionalAsync(this Field field, JToken value, IList<string> errors, ValidationContext context = null)
{ {
return field.ValidateAsync(value, (context ?? EverythingValidContext).Optional(true), errors.Add); return field.ValidateAsync(value, (context ?? ValidContext).Optional(true), errors.Add);
}
public static ValidationContext InvalidContext(Guid assetId)
{
var invalidIds = Task.FromResult<IReadOnlyList<Guid>>(new List<Guid> { assetId });
return new ValidationContext((x, y) => invalidIds, x => invalidIds);
} }
} }
} }

Loading…
Cancel
Save