Browse Source

Cleanup.

pull/239/head
Sebastian Stehle 8 years ago
parent
commit
623e6fc921
  1. 74
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  2. 1
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  3. 47
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/AssetModel.cs
  4. 57
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FilterBuilder.cs
  5. 155
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FilterVisitor.cs
  6. 56
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  7. 41
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/SearchTermVisitor.cs
  8. 31
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  9. 60
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs
  10. 54
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  11. 72
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs
  12. 61
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs
  13. 47
      src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmAssetModel.cs
  14. 49
      src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmAssetQuery.cs
  15. 55
      src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmModelBuilder.cs
  16. 38
      src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmModelExtensions.cs
  17. 24
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/ConstantVisitor.cs
  18. 17
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/FilterBuilder.cs
  19. 29
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/FilterVisitor.cs
  20. 48
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/LimitExtensions.cs
  21. 33
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/PropertyBuilder.cs
  22. 21
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/PropertyNameVisitor.cs
  23. 2
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/SearchTermVisitor.cs
  24. 32
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/SortBuilder.cs
  25. 1
      src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  26. 25
      src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs
  27. 19
      src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs
  28. 3
      src/Squidex/Config/Domain/ReadServices.cs
  29. 2
      src/Squidex/Config/Domain/StoreServices.cs

74
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -9,8 +9,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Edm;
@ -23,12 +21,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository
{
private readonly EdmModelBuilder modelBuilder;
public MongoAssetRepository(IMongoDatabase database, EdmModelBuilder modelBuilder)
public MongoAssetRepository(IMongoDatabase database)
: base(database)
{
this.modelBuilder = modelBuilder;
}
protected override string CollectionName()
@ -48,32 +43,55 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, string query = null)
{
var parsedQuery = ParseQuery(query);
try
{
var odataQuery = EdmAssetQuery.ParseQuery(query);
var filter = FindExtensions.BuildQuery(odataQuery, appId);
var assetEntities =
await Collection
.Find(parsedQuery, appId)
.Skip(parsedQuery)
.Take(parsedQuery)
.SortByDescending(x => x.LastModified)
var contentCount = Collection.Find(filter).CountAsync();
var contentItems =
Collection.Find(filter)
.AssetTake(odataQuery)
.AssetSkip(odataQuery)
.AssetSort(odataQuery)
.ToListAsync();
var assetCount = await Collection.Find(parsedQuery, appId).CountAsync();
await Task.WhenAll(contentItems, contentCount);
return ResultList.Create(assetEntities.OfType<IAssetEntity>().ToList(), assetCount);
return ResultList.Create<IAssetEntity>(contentItems.Result, contentCount.Result);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported.");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported.");
}
catch (MongoQueryException ex)
{
if (ex.Message.Contains("17406"))
{
throw new DomainException("Result set is too large to be retrieved. Use $top parameter to reduce the number of items.");
}
else
{
throw;
}
}
}
public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, HashSet<Guid> ids)
{
var find = Collection
.Find(Filter.In(x => x.Id, ids))
.SortByDescending(x => x.LastModified);
var find = Collection.Find(Filter.In(x => x.Id, ids)).SortByDescending(x => x.LastModified);
var assetEntities = find.ToListAsync();
var assetItems = find.ToListAsync();
var assetCount = find.CountAsync();
await Task.WhenAll(assetEntities, assetCount);
return ResultList.Create(assetEntities.Result.OfType<IAssetEntity>().ToList(), assetCount.Result);
await Task.WhenAll(assetItems, assetCount);
return ResultList.Create(assetItems.Result.OfType<IAssetEntity>().ToList(), assetCount.Result);
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
@ -84,19 +102,5 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return assetEntity;
}
private ODataUriParser ParseQuery(string query)
{
try
{
var model = modelBuilder.EdmModel;
return model.ParseQuery(query);
}
catch (ODataException ex)
{
throw new ValidationException($"Failed to parse query: {ex.Message}", ex);
}
}
}
}

1
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs

@ -34,6 +34,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
public async Task WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion)
{
var entity = SimpleMapper.Map(value, new MongoAssetEntity());
entity.Version = newVersion;
await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, Upsert);

47
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/AssetModel.cs

@ -0,0 +1,47 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.OData.Edm;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
public static class AssetModel
{
public static readonly IEdmModel Edm;
static AssetModel()
{
var entityType = new EdmEntityType("Squidex", "Asset");
entityType.AddStructuralProperty(nameof(MongoAssetEntity.Id), EdmPrimitiveTypeKind.Guid);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.AppId), EdmPrimitiveTypeKind.Guid);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.Created), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.CreatedBy), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.LastModified), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.LastModifiedBy), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.Version), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.FileName), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.FileSize), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.FileVersion), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.IsImage), EdmPrimitiveTypeKind.Boolean);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.MimeType), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.PixelHeight), EdmPrimitiveTypeKind.Int32);
entityType.AddStructuralProperty(nameof(MongoAssetEntity.PixelWidth), EdmPrimitiveTypeKind.Int32);
var container = new EdmEntityContainer("Squidex", "Container");
container.AddEntitySet("AssetSet", entityType);
var model = new EdmModel();
model.AddElement(container);
model.AddElement(entityType);
Edm = model;
}
}
}

57
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FilterBuilder.cs

@ -1,57 +0,0 @@
// ==========================================================================
// FilterBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.OData;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
public static class FilterBuilder
{
private static readonly FilterDefinitionBuilder<MongoAssetEntity> Filter = Builders<MongoAssetEntity>.Filter;
public static List<FilterDefinition<MongoAssetEntity>> Build(ODataUriParser query)
{
List<FilterDefinition<MongoAssetEntity>> filters = new List<FilterDefinition<MongoAssetEntity>>();
SearchClause search;
try
{
search = query.ParseSearch();
}
catch (ODataException ex)
{
throw new ValidationException("Query $search clause not valid.", new ValidationError(ex.Message));
}
if (search != null)
{
filters.Add(Filter.Text(SearchTermVisitor.Visit(search.Expression).ToString()));
}
FilterClause filter;
try
{
filter = query.ParseFilter();
}
catch (ODataException ex)
{
throw new ValidationException("Query $filter clause not valid.", new ValidationError(ex.Message));
}
if (filter != null)
{
filters.Add(FilterVisitor.Visit(filter.Expression));
}
return filters;
}
}
}

155
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FilterVisitor.cs

@ -1,155 +0,0 @@
// ==========================================================================
// FilterVisitor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.UriParser;
using MongoDB.Bson;
using MongoDB.Driver;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
public class FilterVisitor : QueryNodeVisitor<FilterDefinition<MongoAssetEntity>>
{
private static readonly FilterDefinitionBuilder<MongoAssetEntity> Filter = Builders<MongoAssetEntity>.Filter;
public static FilterDefinition<MongoAssetEntity> Visit(QueryNode node)
{
var visitor = new FilterVisitor();
return node.Accept(visitor);
}
public override FilterDefinition<MongoAssetEntity> Visit(ConvertNode nodeIn)
{
return nodeIn.Source.Accept(this);
}
public override FilterDefinition<MongoAssetEntity> Visit(UnaryOperatorNode nodeIn)
{
if (nodeIn.OperatorKind == UnaryOperatorKind.Not)
{
return Filter.Not(nodeIn.Operand.Accept(this));
}
throw new NotSupportedException();
}
public override FilterDefinition<MongoAssetEntity> Visit(SingleValueFunctionCallNode nodeIn)
{
var fieldNode = nodeIn.Parameters.ElementAt(0);
var valueNode = nodeIn.Parameters.ElementAt(1);
if (string.Equals(nodeIn.Name, "endswith", StringComparison.OrdinalIgnoreCase))
{
var value = BuildRegex(valueNode, v => v + "$");
return Filter.Regex(BuildFieldDefinition(fieldNode), value);
}
if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase))
{
var value = BuildRegex(valueNode, v => "^" + v);
return Filter.Regex(BuildFieldDefinition(fieldNode), value);
}
if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase))
{
var value = BuildRegex(valueNode, v => v);
return Filter.Regex(BuildFieldDefinition(fieldNode), value);
}
throw new NotSupportedException();
}
public override FilterDefinition<MongoAssetEntity> Visit(BinaryOperatorNode nodeIn)
{
if (nodeIn.OperatorKind == BinaryOperatorKind.And)
{
return Filter.And(nodeIn.Left.Accept(this), nodeIn.Right.Accept(this));
}
if (nodeIn.OperatorKind == BinaryOperatorKind.Or)
{
return Filter.Or(nodeIn.Left.Accept(this), nodeIn.Right.Accept(this));
}
if (nodeIn.Left is SingleValueFunctionCallNode functionNode)
{
var regexFilter = Visit(functionNode);
var value = BuildValue(nodeIn.Right);
if (value is bool booleanRight)
{
if ((nodeIn.OperatorKind == BinaryOperatorKind.Equal && !booleanRight) ||
(nodeIn.OperatorKind == BinaryOperatorKind.NotEqual && booleanRight))
{
regexFilter = Filter.Not(regexFilter);
}
return regexFilter;
}
}
else
{
if (nodeIn.OperatorKind == BinaryOperatorKind.NotEqual)
{
var field = BuildFieldDefinition(nodeIn.Left);
return Filter.Or(
Filter.Not(Filter.Exists(field)),
Filter.Ne(field, BuildValue(nodeIn.Right)));
}
if (nodeIn.OperatorKind == BinaryOperatorKind.Equal)
{
return Filter.Eq(BuildFieldDefinition(nodeIn.Left), BuildValue(nodeIn.Right));
}
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan)
{
return Filter.Lt(BuildFieldDefinition(nodeIn.Left), BuildValue(nodeIn.Right));
}
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual)
{
return Filter.Lte(BuildFieldDefinition(nodeIn.Left), BuildValue(nodeIn.Right));
}
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan)
{
return Filter.Gt(BuildFieldDefinition(nodeIn.Left), BuildValue(nodeIn.Right));
}
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual)
{
return Filter.Gte(BuildFieldDefinition(nodeIn.Left), BuildValue(nodeIn.Right));
}
}
throw new NotSupportedException();
}
private static BsonRegularExpression BuildRegex(QueryNode node, Func<string, string> formatter)
{
return new BsonRegularExpression(formatter(BuildValue(node).ToString()), "i");
}
private FieldDefinition<MongoAssetEntity, object> BuildFieldDefinition(QueryNode nodeIn)
{
return PropertyVisitor.Visit(nodeIn);
}
private static object BuildValue(QueryNode nodeIn)
{
return ConstantVisitor.Visit(nodeIn);
}
}
}

56
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using Microsoft.OData.UriParser;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure.MongoDb.OData;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
@ -17,48 +18,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
private static readonly FilterDefinitionBuilder<MongoAssetEntity> Filter = Builders<MongoAssetEntity>.Filter;
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> Sort(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query)
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSort(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query)
{
return cursor.Sort(SortBuilder.BuildSort(query));
}
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> Take(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query)
{
var top = query.ParseTop();
var sort = query.BuildSort<MongoAssetEntity>();
if (top.HasValue)
{
cursor = cursor.Limit(Math.Min((int)top.Value, 200));
}
else
{
cursor = cursor.Limit(20);
return sort != null ? cursor.Sort(sort) : cursor.SortByDescending(x => x.LastModified);
}
return cursor;
}
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> Skip(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query)
{
var skip = query.ParseSkip();
if (skip.HasValue)
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetTake(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query)
{
cursor = cursor.Skip((int)skip.Value);
}
else
{
cursor = cursor.Skip(null);
}
return cursor;
return cursor.Take(query, 200, 20);
}
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> Find(this IMongoCollection<MongoAssetEntity> cursor, ODataUriParser query, Guid appId)
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSkip(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ODataUriParser query)
{
var filter = BuildQuery(query, appId);
return cursor.Find(filter);
return cursor.Skip(query);
}
public static FilterDefinition<MongoAssetEntity> BuildQuery(ODataUriParser query, Guid appId)
@ -69,7 +43,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
Filter.Eq(x => x.IsDeleted, false)
};
filters.AddRange(FilterBuilder.Build(query));
var filter = query.BuildFilter<MongoAssetEntity>(null, false);
if (filter.Filter != null)
{
if (filter.Last)
{
filters.Add(filter.Filter);
}
else
{
filters.Insert(0, filter.Filter);
}
}
if (filters.Count > 1)
{

41
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/SearchTermVisitor.cs

@ -1,41 +0,0 @@
// ==========================================================================
// SearchTermVisitor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
public class SearchTermVisitor : QueryNodeVisitor<string>
{
private static readonly SearchTermVisitor Instance = new SearchTermVisitor();
private SearchTermVisitor()
{
}
public static object Visit(QueryNode node)
{
return node.Accept(Instance);
}
public override string Visit(BinaryOperatorNode nodeIn)
{
if (nodeIn.OperatorKind == BinaryOperatorKind.And)
{
return nodeIn.Left.Accept(this) + " " + nodeIn.Right.Accept(this);
}
throw new NotSupportedException();
}
public override string Visit(SearchTermNode nodeIn)
{
return nodeIn.Text;
}
}
}

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

@ -75,24 +75,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
{
FilterDefinition<MongoContentEntity> filter;
try
{
filter = FindExtensions.BuildQuery(odataQuery, schema.Id, schema.SchemaDef, status);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported.");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported.");
}
var propertyCalculator = FindExtensions.CreatePropertyCalculator(schema.SchemaDef);
var filter = FindExtensions.BuildQuery(odataQuery, schema.Id, status, propertyCalculator);
try
{
var contentItems = Collection.Find(filter).Take(odataQuery).Skip(odataQuery).Sort(odataQuery, schema.SchemaDef).ToListAsync();
var contentCount = Collection.Find(filter).CountAsync();
var contentItems =
Collection.Find(filter)
.ContentTake(odataQuery)
.ContentSkip(odataQuery)
.ContentSort(odataQuery, propertyCalculator)
.ToListAsync();
await Task.WhenAll(contentItems, contentCount);
@ -103,6 +98,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return ResultList.Create<IContentEntity>(contentItems.Result, contentCount.Result);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported.");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported.");
}
catch (MongoQueryException ex)
{
if (ex.Message.Contains("17406"))

60
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs

@ -1,60 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
using NodaTime;
using NodaTime.Text;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public sealed class ConstantVisitor : QueryNodeVisitor<object>
{
private static readonly ConstantVisitor Instance = new ConstantVisitor();
private ConstantVisitor()
{
}
public static object Visit(QueryNode node)
{
return node.Accept(Instance);
}
public override object Visit(ConvertNode nodeIn)
{
var booleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean);
if (nodeIn.TypeReference.Definition == booleanType)
{
return bool.Parse(Visit(nodeIn.Source).ToString());
}
var dateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset);
if (nodeIn.TypeReference.Definition == dateTimeType)
{
var value = Visit(nodeIn.Source);
if (value is DateTimeOffset dateTimeOffset)
{
return Instant.FromDateTimeOffset(dateTimeOffset);
}
return InstantPattern.General.Parse(Visit(nodeIn.Source).ToString()).Value;
}
return base.Visit(nodeIn);
}
public override object Visit(ConstantNode nodeIn)
{
return nodeIn.Value;
}
}
}

54
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs

@ -7,10 +7,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.MongoDb.OData;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
@ -18,51 +21,46 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
public static IFindFluent<MongoContentEntity, MongoContentEntity> Sort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query, Schema schema)
public static PropertyCalculator CreatePropertyCalculator(Schema schema)
{
return cursor.Sort(SortBuilder.BuildSort(query, schema));
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Take(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query)
return propertyNames =>
{
var top = query.ParseTop();
if (top.HasValue)
if (propertyNames.Length == 3)
{
cursor = cursor.Limit(Math.Min((int)top.Value, 200));
}
else
var edmName = propertyNames[1].UnescapeEdmField();
if (!schema.FieldsByName.TryGetValue(edmName, out var field))
{
cursor = cursor.Limit(20);
throw new NotSupportedException();
}
return cursor;
propertyNames[1] = field.Id.ToString();
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Skip(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query)
{
var skip = query.ParseSkip();
var propertyName = $"do.{string.Join(".", propertyNames.Skip(1))}";
if (skip.HasValue)
{
cursor = cursor.Skip((int)skip.Value);
return propertyName;
};
}
else
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query, PropertyCalculator propertyCalculator)
{
cursor = cursor.Skip(null);
}
var sort = query.BuildSort<MongoContentEntity>(propertyCalculator);
return cursor;
return sort != null ? cursor.Sort(sort) : cursor.SortByDescending(x => x.LastModified);
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Find(this IMongoCollection<MongoContentEntity> cursor, ODataUriParser query, Guid schemaId, Schema schema, Status[] status)
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentTake(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query)
{
var filter = BuildQuery(query, schemaId, schema, status);
return cursor.Take(query, 200, 20);
}
return cursor.Find(filter);
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSkip(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query)
{
return cursor.Skip(query);
}
public static FilterDefinition<MongoContentEntity> BuildQuery(ODataUriParser query, Guid schemaId, Schema schema, Status[] status)
public static FilterDefinition<MongoContentEntity> BuildQuery(ODataUriParser query, Guid schemaId, Status[] status, PropertyCalculator propertyCalculator)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
@ -71,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
Filter.Eq(x => x.IsDeleted, false)
};
var filter = FilterBuilder.Build(query, schema);
var filter = query.BuildFilter<MongoContentEntity>(propertyCalculator);
if (filter.Filter != null)
{

72
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs

@ -1,72 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public sealed class PropertyVisitor : QueryNodeVisitor<ImmutableList<string>>
{
private static readonly PropertyVisitor Instance = new PropertyVisitor();
public static StringFieldDefinition<MongoContentEntity, object> Visit(QueryNode node, Schema schema)
{
var propertyNames = node.Accept(Instance).ToArray();
if (propertyNames.Length == 3)
{
var edmName = propertyNames[1].UnescapeEdmField();
if (!schema.FieldsByName.TryGetValue(edmName, out var field))
{
throw new NotSupportedException();
}
propertyNames[1] = field.Id.ToString();
}
var propertyName = $"do.{string.Join(".", propertyNames.Skip(1))}";
return new StringFieldDefinition<MongoContentEntity, object>(propertyName);
}
public override ImmutableList<string> Visit(ConvertNode nodeIn)
{
return nodeIn.Source.Accept(this);
}
public override ImmutableList<string> Visit(SingleComplexNode nodeIn)
{
if (nodeIn.Source is SingleComplexNode)
{
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name);
}
else
{
return ImmutableList.Create(nodeIn.Property.Name);
}
}
public override ImmutableList<string> Visit(SingleValuePropertyAccessNode nodeIn)
{
if (nodeIn.Source is SingleComplexNode)
{
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name);
}
else
{
return ImmutableList.Create(nodeIn.Property.Name);
}
}
}
}

61
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs

@ -1,61 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public static class SortBuilder
{
private static readonly SortDefinitionBuilder<MongoContentEntity> Sort = Builders<MongoContentEntity>.Sort;
public static SortDefinition<MongoContentEntity> BuildSort(ODataUriParser query, Schema schema)
{
var orderBy = query.ParseOrderBy();
if (orderBy != null)
{
var sorts = new List<SortDefinition<MongoContentEntity>>();
while (orderBy != null)
{
sorts.Add(OrderBy(orderBy, schema));
orderBy = orderBy.ThenBy;
}
if (sorts.Count > 1)
{
return Sort.Combine(sorts);
}
else
{
return sorts[0];
}
}
else
{
return Sort.Descending(x => x.LastModified);
}
}
public static SortDefinition<MongoContentEntity> OrderBy(OrderByClause clause, Schema schema)
{
if (clause.Direction == OrderByDirection.Ascending)
{
return Sort.Ascending(PropertyVisitor.Visit(clause.Expression, schema));
}
else
{
return Sort.Descending(PropertyVisitor.Visit(clause.Expression, schema));
}
}
}
}

47
src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmAssetModel.cs

@ -0,0 +1,47 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.OData.Edm;
namespace Squidex.Domain.Apps.Entities.Assets.Edm
{
public static class EdmAssetModel
{
public static readonly IEdmModel Edm;
static EdmAssetModel()
{
var entityType = new EdmEntityType("Squidex", "Asset");
entityType.AddStructuralProperty(nameof(IAssetEntity.Id), EdmPrimitiveTypeKind.Guid);
entityType.AddStructuralProperty(nameof(IAssetEntity.AppId), EdmPrimitiveTypeKind.Guid);
entityType.AddStructuralProperty(nameof(IAssetEntity.Created), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(IAssetEntity.CreatedBy), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(IAssetEntity.LastModified), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(IAssetEntity.LastModifiedBy), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(IAssetEntity.Version), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(IAssetEntity.FileName), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(IAssetEntity.FileSize), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(IAssetEntity.FileVersion), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(IAssetEntity.IsImage), EdmPrimitiveTypeKind.Boolean);
entityType.AddStructuralProperty(nameof(IAssetEntity.MimeType), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(IAssetEntity.PixelHeight), EdmPrimitiveTypeKind.Int32);
entityType.AddStructuralProperty(nameof(IAssetEntity.PixelWidth), EdmPrimitiveTypeKind.Int32);
var container = new EdmEntityContainer("Squidex", "Container");
container.AddEntitySet("AssetSet", entityType);
var model = new EdmModel();
model.AddElement(container);
model.AddElement(entityType);
Edm = model;
}
}
}

49
src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmAssetQuery.cs

@ -0,0 +1,49 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.Edm
{
public static class EdmAssetQuery
{
public static ODataUriParser ParseQuery(string query)
{
try
{
var model = EdmAssetModel.Edm;
if (!model.EntityContainer.EntitySets().Any())
{
return null;
}
query = query ?? string.Empty;
var path = model.EntityContainer.EntitySets().First().Path.Path.Split('.').Last();
if (query.StartsWith("?", StringComparison.Ordinal))
{
query = query.Substring(1);
}
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative));
return parser;
}
catch (ODataException ex)
{
throw new ValidationException($"Failed to parse query: {ex.Message}", ex);
}
}
}
}

55
src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmModelBuilder.cs

@ -1,55 +0,0 @@
// ==========================================================================
// EdmModelBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Entities.Assets.State;
namespace Squidex.Domain.Apps.Entities.Assets.Edm
{
public class EdmModelBuilder
{
private readonly IEdmModel edmModel;
public EdmModelBuilder()
{
edmModel = BuildEdmModel();
}
public virtual IEdmModel EdmModel
{
get { return edmModel; }
}
private IEdmModel BuildEdmModel()
{
var model = new EdmModel();
var container = new EdmEntityContainer("Squidex", "Container");
var entityType = new EdmEntityType("Squidex", "Asset");
entityType.AddStructuralProperty(nameof(AssetState.Id), EdmPrimitiveTypeKind.Guid);
entityType.AddStructuralProperty(nameof(AssetState.AppId), EdmPrimitiveTypeKind.Guid);
entityType.AddStructuralProperty(nameof(AssetState.Created), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(AssetState.CreatedBy), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(AssetState.LastModified), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(AssetState.LastModifiedBy), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(AssetState.Version), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(AssetState.FileName), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(AssetState.FileSize), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(AssetState.FileVersion), EdmPrimitiveTypeKind.Int64);
entityType.AddStructuralProperty(nameof(AssetState.IsImage), EdmPrimitiveTypeKind.Boolean);
entityType.AddStructuralProperty(nameof(AssetState.MimeType), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(AssetState.PixelHeight), EdmPrimitiveTypeKind.Int32);
entityType.AddStructuralProperty(nameof(AssetState.PixelWidth), EdmPrimitiveTypeKind.Int32);
model.AddElement(container);
model.AddElement(entityType);
container.AddEntitySet("AssetSet", entityType);
return model;
}
}
}

38
src/Squidex.Domain.Apps.Entities/Assets/Edm/EdmModelExtensions.cs

@ -1,38 +0,0 @@
// ==========================================================================
// EdmModelExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Entities.Assets.Edm
{
public static class EdmModelExtensions
{
public static ODataUriParser ParseQuery(this IEdmModel model, string query)
{
if (!model.EntityContainer.EntitySets().Any())
{
return null;
}
query = query ?? string.Empty;
var path = model.EntityContainer.EntitySets().First().Path.Path.Split('.').Last();
if (query.StartsWith("?", StringComparison.Ordinal))
{
query = query.Substring(1);
}
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative));
return parser;
}
}
}

24
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/ConstantVisitor.cs → src/Squidex.Infrastructure.MongoDb/MongoDb/OData/ConstantVisitor.cs

@ -11,10 +11,14 @@ using Microsoft.OData.UriParser;
using NodaTime;
using NodaTime.Text;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
namespace Squidex.Infrastructure.MongoDb.OData
{
public sealed class ConstantVisitor : QueryNodeVisitor<object>
{
private static readonly IEdmPrimitiveType BooleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean);
private static readonly IEdmPrimitiveType DateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset);
private static readonly IEdmPrimitiveType GuidType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Guid);
private static readonly ConstantVisitor Instance = new ConstantVisitor();
private ConstantVisitor()
@ -28,16 +32,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
public override object Visit(ConvertNode nodeIn)
{
var booleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean);
if (nodeIn.TypeReference.Definition == booleanType)
if (nodeIn.TypeReference.Definition == BooleanType)
{
return bool.Parse(Visit(nodeIn.Source).ToString());
}
var dateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset);
if (nodeIn.TypeReference.Definition == GuidType)
{
return Guid.Parse(Visit(nodeIn.Source).ToString());
}
if (nodeIn.TypeReference.Definition == dateTimeType)
if (nodeIn.TypeReference.Definition == DateTimeType)
{
var value = Visit(nodeIn.Source);
@ -49,13 +54,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
return InstantPattern.General.Parse(Visit(nodeIn.Source).ToString()).Value;
}
var guidType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Guid);
if (nodeIn.TypeReference.Definition == guidType)
{
return Guid.Parse(Visit(nodeIn.Source).ToString());
}
return base.Visit(nodeIn);
}

17
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs → src/Squidex.Infrastructure.MongoDb/MongoDb/OData/FilterBuilder.cs

@ -8,16 +8,12 @@
using Microsoft.OData;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
namespace Squidex.Infrastructure.MongoDb.OData
{
public static class FilterBuilder
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
public static (FilterDefinition<MongoContentEntity> Filter, bool Last) Build(ODataUriParser query, Schema schema)
public static (FilterDefinition<T> Filter, bool Last) BuildFilter<T>(this ODataUriParser query, PropertyCalculator propertyCalculator = null, bool supportsSearch = true)
{
SearchClause search;
try
@ -31,7 +27,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
if (search != null)
{
return (Filter.Text(SearchTermVisitor.Visit(search.Expression).ToString()), false);
if (!supportsSearch)
{
throw new ValidationException("Query $search clause not supported.");
}
return (Builders<T>.Filter.Text(SearchTermVisitor.Visit(search.Expression).ToString()), false);
}
FilterClause filter;
@ -46,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
if (filter != null)
{
return (FilterVisitor.Visit(filter.Expression, schema), true);
return (FilterVisitor<T>.Visit(filter.Expression, propertyCalculator), true);
}
return (null, false);

29
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs → src/Squidex.Infrastructure.MongoDb/MongoDb/OData/FilterVisitor.cs

@ -10,33 +10,32 @@ using System.Linq;
using Microsoft.OData.UriParser;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
namespace Squidex.Infrastructure.MongoDb.OData
{
public class FilterVisitor : QueryNodeVisitor<FilterDefinition<MongoContentEntity>>
public sealed class FilterVisitor<T> : QueryNodeVisitor<FilterDefinition<T>>
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
private readonly Schema schema;
private static readonly FilterDefinitionBuilder<T> Filter = Builders<T>.Filter;
private readonly PropertyCalculator propertyCalculator;
private FilterVisitor(Schema schema)
private FilterVisitor(PropertyCalculator propertyCalculator)
{
this.schema = schema;
this.propertyCalculator = propertyCalculator;
}
public static FilterDefinition<MongoContentEntity> Visit(QueryNode node, Schema schema)
public static FilterDefinition<T> Visit(QueryNode node, PropertyCalculator propertyCalculator)
{
var visitor = new FilterVisitor(schema);
var visitor = new FilterVisitor<T>(propertyCalculator);
return node.Accept(visitor);
}
public override FilterDefinition<MongoContentEntity> Visit(ConvertNode nodeIn)
public override FilterDefinition<T> Visit(ConvertNode nodeIn)
{
return nodeIn.Source.Accept(this);
}
public override FilterDefinition<MongoContentEntity> Visit(UnaryOperatorNode nodeIn)
public override FilterDefinition<T> Visit(UnaryOperatorNode nodeIn)
{
if (nodeIn.OperatorKind == UnaryOperatorKind.Not)
{
@ -46,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
throw new NotSupportedException();
}
public override FilterDefinition<MongoContentEntity> Visit(SingleValueFunctionCallNode nodeIn)
public override FilterDefinition<T> Visit(SingleValueFunctionCallNode nodeIn)
{
var fieldNode = nodeIn.Parameters.ElementAt(0);
var valueNode = nodeIn.Parameters.ElementAt(1);
@ -75,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
throw new NotSupportedException();
}
public override FilterDefinition<MongoContentEntity> Visit(BinaryOperatorNode nodeIn)
public override FilterDefinition<T> Visit(BinaryOperatorNode nodeIn)
{
if (nodeIn.OperatorKind == BinaryOperatorKind.And)
{
@ -149,9 +148,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return new BsonRegularExpression(formatter(BuildValue(node).ToString()), "i");
}
private FieldDefinition<MongoContentEntity, object> BuildFieldDefinition(QueryNode nodeIn)
private FieldDefinition<T, object> BuildFieldDefinition(QueryNode nodeIn)
{
return PropertyVisitor.Visit(nodeIn, schema);
return nodeIn.BuildFieldDefinition<T>(propertyCalculator);
}
private static object BuildValue(QueryNode nodeIn)

48
src/Squidex.Infrastructure.MongoDb/MongoDb/OData/LimitExtensions.cs

@ -0,0 +1,48 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
namespace Squidex.Infrastructure.MongoDb.OData
{
public static class LimitExtensions
{
public static IFindFluent<T, T> Take<T>(this IFindFluent<T, T> cursor, ODataUriParser query, int maxValue = 200, int defaultValue = 20)
{
var top = query.ParseTop();
if (top.HasValue)
{
cursor = cursor.Limit(Math.Min((int)top.Value, maxValue));
}
else
{
cursor = cursor.Limit(defaultValue);
}
return cursor;
}
public static IFindFluent<T, T> Skip<T>(this IFindFluent<T, T> cursor, ODataUriParser query)
{
var skip = query.ParseSkip();
if (skip.HasValue)
{
cursor = cursor.Skip((int)skip.Value);
}
else
{
cursor = cursor.Skip(null);
}
return cursor;
}
}
}

33
src/Squidex.Infrastructure.MongoDb/MongoDb/OData/PropertyBuilder.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
namespace Squidex.Infrastructure.MongoDb.OData
{
public delegate string PropertyCalculator(string[] parts);
public static class PropertyBuilder
{
private static readonly PropertyCalculator DefaultCalculator = parts =>
{
return string.Join(".", parts);
};
public static StringFieldDefinition<T, object> BuildFieldDefinition<T>(this QueryNode node, PropertyCalculator propertyCalculator)
{
propertyCalculator = propertyCalculator ?? DefaultCalculator;
var propertyParts = node.Accept(PropertyNameVisitor.Instance).ToArray();
var propertyName = propertyCalculator(propertyParts);
return new StringFieldDefinition<T, object>(propertyName);
}
}
}

21
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/PropertyVisitor.cs → src/Squidex.Infrastructure.MongoDb/MongoDb/OData/PropertyNameVisitor.cs

@ -1,26 +1,23 @@
// ==========================================================================
// PropertyVisitor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using System.Linq;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.MongoDb.OData
{
public sealed class PropertyVisitor : QueryNodeVisitor<ImmutableList<string>>
public sealed class PropertyNameVisitor : QueryNodeVisitor<ImmutableList<string>>
{
private static readonly PropertyVisitor Instance = new PropertyVisitor();
public static readonly PropertyNameVisitor Instance = new PropertyNameVisitor();
public static StringFieldDefinition<MongoAssetEntity, object> Visit(QueryNode node)
private PropertyNameVisitor()
{
var propertyNames = node.Accept(Instance).ToArray();
return new StringFieldDefinition<MongoAssetEntity, object>(propertyNames.First());
}
public override ImmutableList<string> Visit(ConvertNode nodeIn)

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs → src/Squidex.Infrastructure.MongoDb/MongoDb/OData/SearchTermVisitor.cs

@ -8,7 +8,7 @@
using System;
using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
namespace Squidex.Infrastructure.MongoDb.OData
{
public class SearchTermVisitor : QueryNodeVisitor<string>
{

32
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/SortBuilder.cs → src/Squidex.Infrastructure.MongoDb/MongoDb/OData/SortBuilder.cs

@ -1,59 +1,57 @@
// ==========================================================================
// SortBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.OData.UriParser;
using MongoDB.Driver;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
namespace Squidex.Infrastructure.MongoDb.OData
{
public static class SortBuilder
{
private static readonly SortDefinitionBuilder<MongoAssetEntity> Sort = Builders<MongoAssetEntity>.Sort;
public static SortDefinition<MongoAssetEntity> BuildSort(ODataUriParser query)
public static SortDefinition<T> BuildSort<T>(this ODataUriParser query, PropertyCalculator propertyCalculator = null)
{
var orderBy = query.ParseOrderBy();
if (orderBy != null)
{
var sorts = new List<SortDefinition<MongoAssetEntity>>();
var sorts = new List<SortDefinition<T>>();
while (orderBy != null)
{
sorts.Add(OrderBy(orderBy));
sorts.Add(OrderBy<T>(orderBy, propertyCalculator));
orderBy = orderBy.ThenBy;
}
if (sorts.Count > 1)
{
return Sort.Combine(sorts);
return Builders<T>.Sort.Combine(sorts);
}
else
{
return sorts[0];
}
}
else
{
return Sort.Descending(x => x.LastModified);
}
return null;
}
public static SortDefinition<MongoAssetEntity> OrderBy(OrderByClause clause)
public static SortDefinition<T> OrderBy<T>(OrderByClause clause, PropertyCalculator propertyCalculator = null)
{
var propertyName = clause.Expression.BuildFieldDefinition<T>(propertyCalculator);
if (clause.Direction == OrderByDirection.Ascending)
{
return Sort.Ascending(PropertyVisitor.Visit(clause.Expression));
return Builders<T>.Sort.Ascending(propertyName);
}
else
{
return Sort.Descending(PropertyVisitor.Visit(clause.Expression));
return Builders<T>.Sort.Descending(propertyName);
}
}
}

1
src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -11,6 +11,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.4.0" />
<PackageReference Include="MongoDB.Driver" Version="2.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

25
src/Squidex/Docs/AddODataQueryParams.cs → src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs

@ -13,17 +13,32 @@ using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Infrastructure.Tasks;
using Squidex.Pipeline.Swagger;
namespace Squidex.Docs
namespace Squidex.Areas.Api.Config.Swagger
{
public class AddODataQueryParams : IOperationProcessor
public sealed class ODataQueryParamsProcessor : IOperationProcessor
{
private readonly string path;
private readonly string entity;
private readonly bool supportSearch;
public ODataQueryParamsProcessor(string path, string entity, bool supportSearch)
{
this.path = path;
this.entity = entity;
this.supportSearch = supportSearch;
}
public Task<bool> ProcessAsync(OperationProcessorContext context)
{
if (context.OperationDescription.Path == "/apps/{app}/assets")
if (context.OperationDescription.Path == path)
{
if (supportSearch)
{
context.OperationDescription.Operation.AddQueryParameter("$top", JsonObjectType.Number, "Optional number of contents to take.");
context.OperationDescription.Operation.AddQueryParameter("$skip", JsonObjectType.Number, "Optional number of contents to skip.");
context.OperationDescription.Operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search.");
}
context.OperationDescription.Operation.AddQueryParameter("$top", JsonObjectType.Number, $"Optional number of {entity} to take.");
context.OperationDescription.Operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip.");
context.OperationDescription.Operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition.");
context.OperationDescription.Operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition.");
}

19
src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs

@ -29,16 +29,8 @@ namespace Squidex.Areas.Api.Config.Swagger
var urlOptions = s.GetService<IOptions<MyUrlsOptions>>().Value;
var settings =
new SwaggerSettings
{
Title = "Squidex API",
Version = "1.0",
IsAspNetCore = false,
OperationProcessors =
{
new Docs.AddODataQueryParams()
}
}
new SwaggerSettings { Title = "Squidex API", Version = "1.0", IsAspNetCore = false }
.AddAssetODataParams()
.ConfigurePaths(urlOptions)
.ConfigureSchemaSettings()
.ConfigureIdentity(urlOptions);
@ -49,6 +41,13 @@ namespace Squidex.Areas.Api.Config.Swagger
services.AddTransient<SchemasSwaggerGenerator>();
}
private static SwaggerSettings AddAssetODataParams(this SwaggerSettings settings)
{
settings.OperationProcessors.Add(new ODataQueryParamsProcessor("/apps/{app}/assets", "assets", false));
return settings;
}
private static SwaggerSettings ConfigureIdentity(this SwaggerSettings settings, MyUrlsOptions urlOptions)
{
settings.DocumentProcessors.Add(

3
src/Squidex/Config/Domain/ReadServices.cs

@ -120,9 +120,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<EdmModelBuilder>()
.AsSelf();
services.AddSingletonAs<Squidex.Domain.Apps.Entities.Assets.Edm.EdmModelBuilder>()
.AsSelf();
services.AddSingletonAs<RuleService>()
.AsSelf();
}

2
src/Squidex/Config/Domain/StoreServices.cs

@ -102,7 +102,7 @@ namespace Squidex.Config.Domain
.As<ISnapshotStore<AppState, Guid>>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoAssetRepository(mongoDatabase, c.GetRequiredService<EdmModelBuilder>()))
services.AddSingletonAs(c => new MongoAssetRepository(mongoDatabase))
.As<IAssetRepository>()
.As<ISnapshotStore<AssetState, Guid>>()
.As<IInitializable>();

Loading…
Cancel
Save