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. 18
      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.
// ==========================================================================
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
@ -22,9 +23,9 @@ namespace Squidex.Core
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
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; }
}
@ -39,10 +40,12 @@ namespace Squidex.Core
this.partitionResolver = partitionResolver;
}
public async Task ValidatePartialAsync(NamedContentData data)
public Task ValidatePartialAsync(NamedContentData data)
{
Guard.NotNull(data, nameof(data));
var tasks = new List<Task>();
foreach (var fieldData in data)
{
var fieldName = fieldData.Key;
@ -53,41 +56,51 @@ namespace Squidex.Core
}
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 partition = partitionResolver(partitioning);
var tasks = new List<Task>();
foreach (var partitionValues in fieldData)
{
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
{
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));
ValidateUnknownFields(data);
var tasks = new List<Task>();
foreach (var field in schema.FieldsByName.Values)
{
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)
@ -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 partition = partitionResolver(partitioning);
var tasks = new List<Task>();
foreach (var partitionValues in fieldData)
{
if (!partition.TryGetItem(partitionValues.Key, out var _))
@ -118,8 +133,10 @@ namespace Squidex.Core
{
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.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas;
@ -18,17 +18,17 @@ namespace Squidex.Core
{
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);
}
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);
}
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)
{

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

@ -7,6 +7,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
@ -14,22 +15,22 @@ namespace Squidex.Core.Schemas
{
public sealed class ValidationContext
{
private readonly Func<Guid, Guid, Task<bool>> checkContent;
private readonly Func<Guid, Task<bool>> checkAsset;
private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent;
private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset;
public bool IsOptional { get; }
public ValidationContext(
Func<Guid, Guid, Task<bool>> checkContent,
Func<Guid, Task<bool>> checkAsset)
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset)
: this(checkContent, checkAsset, false)
{
}
private ValidationContext(
Func<Guid, Guid, Task<bool>> checkContent,
Func<Guid, Task<bool>> checkAsset,
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<Guid>>> checkAsset,
bool isOptional)
{
Guard.NotNull(checkAsset, nameof(checkAsset));
@ -46,14 +47,14 @@ namespace Squidex.Core.Schemas
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.Linq;
using System.Threading.Tasks;
namespace Squidex.Core.Schemas.Validators
@ -35,21 +34,12 @@ namespace Squidex.Core.Schemas.Validators
return;
}
var assetTasks = assets.AssetIds.Select(x => CheckAssetAsync(context, x)).ToArray();
var invalidIds = await context.GetInvalidAssetIdsAsync(assets.AssetIds);
await Task.WhenAll(assetTasks);
foreach (var notFoundId in assetTasks.Where(x => !x.Result.IsFound).Select(x => x.Result.AssetId))
foreach (var invalidId in invalidIds)
{
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);
}
}
}

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

@ -7,7 +7,6 @@
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Squidex.Core.Schemas.Validators
@ -37,21 +36,12 @@ namespace Squidex.Core.Schemas.Validators
return;
}
var referenceTasks = references.ContentIds.Select(x => CheckReferenceAsync(context, x)).ToArray();
await Task.WhenAll(referenceTasks);
foreach (var notFoundId in referenceTasks.Where(x => !x.Result.IsFound).Select(x => x.Result.ReferenceId))
var invalidIds = await context.GetInvalidContentIdsAsync(references.ContentIds, schemaId);
foreach (var invalidId in invalidIds)
{
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 =
await Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.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(IndexKeys.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.Ascending(x => x.EventsOffset), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.EventStreamOffset).Ascending(x => x.EventStream), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(Index.Descending(x => x.EventsOffset), new CreateIndexOptions { Unique = true }),
collection.Indexes.CreateOneAsync(Index.Descending(x => x.EventStreamOffset).Ascending(x => x.EventStream), new CreateIndexOptions { Unique = true }));
eventsOffsetIndex = indexNames[0];
}
@ -201,7 +201,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{
var document =
await Collection.Find(x => x.EventsOffset <= startEventNumber)
.Project<BsonDocument>(Projection
.Project<BsonDocument>(Project
.Include(x => x.EventsOffset))
.SortByDescending(x => x.EventsOffset).Limit(1)
.FirstOrDefaultAsync();
@ -218,7 +218,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{
var document =
await Collection.Find(new BsonDocument())
.Project<BsonDocument>(Projection
.Project<BsonDocument>(Project
.Include(x => x.EventsOffset)
.Include(x => x.EventsCount))
.SortByDescending(x => x.EventsOffset).Limit(1)
@ -236,7 +236,7 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{
var document =
await Collection.Find(x => x.EventStream == streamName)
.Project<BsonDocument>(Projection
.Project<BsonDocument>(Project
.Include(x => x.EventStreamOffset)
.Include(x => x.EventsCount))
.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)
{
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)
{
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)
{
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)
{
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
{
@ -62,7 +62,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
protected static IndexKeysDefinitionBuilder<TEntity> IndexKeys
protected static IndexKeysDefinitionBuilder<TEntity> Index
{
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)
{
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)

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

@ -31,7 +31,7 @@ namespace Squidex.Read.MongoDb.Apps
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()
@ -41,26 +41,29 @@ namespace Squidex.Read.MongoDb.Apps
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId)
{
var entities =
await Collection.Find(s => s.Contributors.ContainsKey(subjectId)).ToListAsync();
var appEntities =
await Collection.Find(s => s.Contributors.ContainsKey(subjectId))
.ToListAsync();
return entities;
return appEntities;
}
public async Task<IAppEntity> FindAppAsync(Guid id)
{
var entity =
await Collection.Find(s => s.Id == id).FirstOrDefaultAsync();
var appEntity =
await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return entity;
return appEntity;
}
public async Task<IAppEntity> FindAppAsync(string name)
{
var entity =
await Collection.Find(s => s.Name == name).FirstOrDefaultAsync();
var appEntity =
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.Repositories;
// ReSharper disable ClassNeverInstantiated.Local
namespace Squidex.Read.MongoDb.Assets
{
public partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, IEventConsumer
@ -33,40 +35,47 @@ namespace Squidex.Read.MongoDb.Assets
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)
{
var filter = CreateFilter(appId, mimeTypes, ids, query);
var assets =
await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified).ToListAsync();
var assetEntities =
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)
{
var filter = CreateFilter(appId, mimeTypes, ids, query);
var count =
await Collection.Find(filter).CountAsync();
var assetsCount =
await Collection.Find(filter)
.CountAsync();
return count;
return assetsCount;
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
{
var entity =
await Collection.Find(s => s.Id == id).FirstOrDefaultAsync();
var assetEntity =
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)

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

@ -34,16 +34,19 @@ namespace Squidex.Read.MongoDb.Assets
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.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).Ascending(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)
{
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 sizesDictionary = originalSizes.ToDictionary(x => x.Date);
var sizesDictionary = originalSizesEntities.ToDictionary(x => x.Date);
var previousSize = long.MinValue;
var previousCount = long.MinValue;
@ -61,10 +64,12 @@ namespace Squidex.Read.MongoDb.Assets
{
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;
previousCount = firstBeforeRange?.TotalCount ?? 0L;
previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L;
previousCount = firstBeforeRangeEntity?.TotalCount ?? 0L;
}
size = new MongoAssetStatsEntity
@ -83,9 +88,11 @@ namespace Squidex.Read.MongoDb.Assets
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 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,
Date = date,
AppId = appId,
TotalSize = last?.TotalSize ?? 0,
TotalCount = last?.TotalCount ?? 0
TotalSize = lastEntity?.TotalSize ?? 0,
TotalCount = lastEntity?.TotalCount ?? 0
};
}
entity.TotalSize += size;
entity.TotalCount += count;
assetStatsEntity.TotalSize += size;
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.Threading.Tasks;
using Microsoft.OData.Core;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure;
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
{
@ -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)
{
List<IContentEntity> result = null;
var contentEntities = (List<IContentEntity>)null;
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{
@ -106,15 +115,15 @@ namespace Squidex.Read.MongoDb.Contents
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)
{
var result = 0L;
var contentsCount = 0L;
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);
}
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 =>
{
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)
{
MongoContentEntity result = null;
var contentEntity = (MongoContentEntity)null;
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)

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 =>
{
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished));
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.DataText));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsPublished));
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(
collection.Indexes.CreateOneAsync(
IndexKeys
Index
.Ascending(x => x.AppId)
.Ascending(x => x.Channel)
.Descending(x => x.Created)
.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)
{
var entities =
var historyEventEntities =
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)

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

@ -40,8 +40,8 @@ namespace Squidex.Read.MongoDb.Infrastructure
protected override Task SetupCollectionAsync(IMongoCollection<PersistedGrant> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.ClientId)),
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SubjectId)));
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ClientId)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SubjectId)));
}
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)
{
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)
{
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)
{
var entity =
var schemaEntity =
await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted)
.FirstOrDefaultAsync();
entity?.DeserializeSchema(serializer);
schemaEntity?.DeserializeSchema(serializer);
return entity;
return schemaEntity;
}
public async Task<ISchemaEntity> FindSchemaAsync(Guid schemaId)
{
var entity =
var schemaEntity =
await Collection.Find(s => s.Id == schemaId)
.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)
{
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)
{
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)
@ -69,7 +69,9 @@ namespace Squidex.Read.MongoDb.Schemas
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)
{

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

@ -23,4 +23,9 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
<PackageReference Include="Microsoft.OData.Core" Version="6.15.0" />
</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>

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<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);
}

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<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);
}

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

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Core;
using Squidex.Core.Schemas;
@ -21,6 +22,8 @@ using Squidex.Read.Contents.Repositories;
using Squidex.Read.Schemas.Services;
using Squidex.Write.Contents.Commands;
// ReSharper disable ConvertToLambdaExpression
namespace Squidex.Write.Contents
{
public class ContentCommandHandler : ICommandHandler
@ -112,7 +115,16 @@ namespace Squidex.Write.Contents
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);

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

@ -64,7 +64,7 @@ namespace Squidex.Config.Identity
async Task userInitAsync(IUser theUser)
{
await userManager.RemovePasswordAsync(theUser);
await userManager.ChangePasswordAsync(theUser, null, adminEmail);
await userManager.ChangePasswordAsync(theUser, null, adminPass);
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="drop-area-container" *ngIf="schema && !isDisabled">
<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>

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

@ -12,7 +12,6 @@ using FluentAssertions;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Core
@ -21,7 +20,7 @@ namespace Squidex.Core
{
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.DE, Language.EN);
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());
[Fact]

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

@ -12,7 +12,6 @@ using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Core.Schemas
@ -20,7 +19,6 @@ namespace Squidex.Core.Schemas
public class AssetsFieldTests
{
private readonly List<string> errors = new List<string>();
private static readonly ValidationContext InvalidAssetContext = new ValidationContext((x, y) => TaskHelper.False, x => TaskHelper.False);
[Fact]
public void Should_instantiate_field()
@ -100,7 +98,7 @@ namespace Squidex.Core.Schemas
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(
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 FluentAssertions;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Core.Schemas
@ -21,7 +20,6 @@ namespace Squidex.Core.Schemas
{
private readonly List<string> errors = new List<string>();
private readonly Guid schemaId = Guid.NewGuid();
private static readonly ValidationContext InvalidSchemaContext = new ValidationContext((x, y) => TaskHelper.False, x => TaskHelper.False);
[Fact]
public void Should_instantiate_field()
@ -46,7 +44,7 @@ namespace Squidex.Core.Schemas
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);
}
@ -101,7 +99,7 @@ namespace Squidex.Core.Schemas
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(
new[] { $"<FIELD> contains invalid reference '{referenceId}'" });

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

@ -6,36 +6,45 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Core.Schemas
{
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)
{
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)
{
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)
{
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)
{
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