Browse Source

OData Support

pull/1/head
Sebastian 9 years ago
parent
commit
cff5838d7e
  1. 2
      src/Squidex.Core/Contents/ContentData.cs
  2. 2
      src/Squidex.Core/Contents/ContentFieldData.cs
  3. 7
      src/Squidex.Core/Schemas/BooleanField.cs
  4. 27
      src/Squidex.Core/Schemas/Field.cs
  5. 7
      src/Squidex.Core/Schemas/NumberField.cs
  6. 23
      src/Squidex.Core/Schemas/Schema.cs
  7. 17
      src/Squidex.Core/Schemas/StringField.cs
  8. 1
      src/Squidex.Core/Schemas/Validators/PatternValidator.cs
  9. 1
      src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs
  10. 1
      src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
  11. 1
      src/Squidex.Infrastructure/CQRS/Events/EventDataFormatter.cs
  12. 1
      src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
  13. 1
      src/Squidex.Infrastructure/CollectionExtensions.cs
  14. 2
      src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs
  15. 2
      src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs
  16. 2
      src/Squidex.Infrastructure/Dispatching/FuncContextDispatcherFactory.cs
  17. 2
      src/Squidex.Infrastructure/Dispatching/FuncDispatcherFactory.cs
  18. 1
      src/Squidex.Infrastructure/Guard.cs
  19. 1
      src/Squidex.Infrastructure/Reflection/SimpleMapper.cs
  20. 1
      src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
  21. 5
      src/Squidex.Read/Contents/Repositories/IContentRepository.cs
  22. 1
      src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
  23. 1
      src/Squidex.Store.MongoDb/Contents/MongoContentEntity.cs
  24. 137
      src/Squidex.Store.MongoDb/Contents/MongoContentRepository.cs
  25. 32
      src/Squidex.Store.MongoDb/Contents/Visitors/ConstantVisitor.cs
  26. 39
      src/Squidex.Store.MongoDb/Contents/Visitors/FilterBuilder.cs
  27. 95
      src/Squidex.Store.MongoDb/Contents/Visitors/FilterVisitor.cs
  28. 72
      src/Squidex.Store.MongoDb/Contents/Visitors/FindExtensions.cs
  29. 63
      src/Squidex.Store.MongoDb/Contents/Visitors/PropertyVisitor.cs
  30. 58
      src/Squidex.Store.MongoDb/Contents/Visitors/SchemaExtensions.cs
  31. 66
      src/Squidex.Store.MongoDb/Contents/Visitors/SortBuilder.cs
  32. 1
      src/Squidex.Write/Apps/AppContributors.cs
  33. 12
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  34. 9
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

2
src/Squidex.Core/Contents/ContentData.cs

@ -39,7 +39,7 @@ namespace Squidex.Core.Contents
foreach (var fieldValue in this)
{
var fieldId = 0L;
long fieldId;
Field field;

2
src/Squidex.Core/Contents/ContentFieldData.cs

@ -22,7 +22,7 @@ namespace Squidex.Core.Contents
public ContentFieldData SetValue(JToken value)
{
this["iv"] = value;
this[Language.Invariant.Iso2Code] = value;
return this;
}

7
src/Squidex.Core/Schemas/BooleanField.cs

@ -7,6 +7,8 @@
// ==========================================================================
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Schemas.Validators;
@ -37,5 +39,10 @@ namespace Squidex.Core.Schemas
{
jsonProperty.Type = JsonObjectType.Boolean;
}
protected override IEdmTypeReference CreateEdmType()
{
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Boolean, !Properties.IsRequired);
}
}
}

27
src/Squidex.Core/Schemas/Field.cs

@ -9,6 +9,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Infrastructure;
@ -129,6 +131,27 @@ namespace Squidex.Core.Schemas
return Clone<Field>(clone => clone.name = newName);
}
public void AddToEdmType(EdmStructuredType edmType, IEnumerable<Language> languages, string schemaName, Func<EdmComplexType, EdmComplexType> typeResolver)
{
Guard.NotNull(edmType, nameof(edmType));
Guard.NotNull(languages, nameof(languages));
Guard.NotNull(typeResolver, nameof(typeResolver));
if (!RawProperties.IsLocalizable)
{
languages = new[] { Language.Invariant };
}
var languageType = typeResolver(new EdmComplexType("Squidex", $"{schemaName}_{Name}_Property"));
foreach (var language in languages)
{
languageType.AddStructuralProperty(language.Iso2Code, CreateEdmType());
}
edmType.AddStructuralProperty(Name, new EdmComplexTypeReference(languageType, false));
}
public void AddToSchema(JsonSchema4 schema, IEnumerable<Language> languages, string schemaName, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotNull(schema, nameof(schema));
@ -152,7 +175,7 @@ namespace Squidex.Core.Schemas
languagesObject.Properties.Add(language.Iso2Code, languageProperty);
}
languagesProperty.AllOf.Add(schemaResolver($"{schemaName}{Name}Property", languagesObject));
languagesProperty.AllOf.Add(schemaResolver($"{schemaName}_{Name}_Property", languagesObject));
schema.Properties.Add(Name, languagesProperty);
}
@ -180,6 +203,8 @@ namespace Squidex.Core.Schemas
protected abstract IEnumerable<IValidator> CreateValidators();
protected abstract IEdmTypeReference CreateEdmType();
protected abstract void PrepareJsonSchema(JsonProperty jsonProperty);
protected abstract object ConvertValue(JToken value);

7
src/Squidex.Core/Schemas/NumberField.cs

@ -8,6 +8,8 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Schemas.Validators;
@ -54,6 +56,11 @@ namespace Squidex.Core.Schemas
}
}
protected override IEdmTypeReference CreateEdmType()
{
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, !Properties.IsRequired);
}
protected override object ConvertValue(JToken value)
{
return (double?)value;

23
src/Squidex.Core/Schemas/Schema.cs

@ -11,11 +11,11 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Contents;
using Squidex.Infrastructure;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable InvertIf
@ -173,6 +173,21 @@ namespace Squidex.Core.Schemas
return AddOrUpdateField(newField);
}
public EdmComplexType BuildEdmType(HashSet<Language> languages, Func<EdmComplexType, EdmComplexType> typeResolver)
{
Guard.NotEmpty(languages, nameof(languages));
Guard.NotNull(typeResolver, nameof(typeResolver));
var edmType = new EdmComplexType("Squidex", Name);
foreach (var field in fieldsByName.Values.Where(x => !x.IsHidden))
{
field.AddToEdmType(edmType, languages, Name, typeResolver);
}
return edmType;
}
public JsonSchema4 BuildSchema(HashSet<Language> languages, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
Guard.NotEmpty(languages, nameof(languages));
@ -233,12 +248,12 @@ namespace Squidex.Core.Schemas
}
else
{
if (fieldData.Keys.Any(x => x != "iv"))
if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language (iv)");
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
}
var value = fieldData.GetOrCreate("iv", k => JValue.CreateNull());
var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
await field.ValidateAsync(value, fieldErrors);
}

17
src/Squidex.Core/Schemas/StringField.cs

@ -9,6 +9,8 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Schemas.Validators;
@ -45,11 +47,6 @@ namespace Squidex.Core.Schemas
}
}
protected override object ConvertValue(JToken value)
{
return value.ToString();
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty)
{
jsonProperty.Type = JsonObjectType.String;
@ -62,5 +59,15 @@ namespace Squidex.Core.Schemas
jsonProperty.EnumerationNames = new Collection<string>(Properties.AllowedValues);
}
}
protected override IEdmTypeReference CreateEdmType()
{
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, !Properties.IsRequired);
}
protected override object ConvertValue(JToken value)
{
return value.ToString();
}
}
}

1
src/Squidex.Core/Schemas/Validators/PatternValidator.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable InvertIf

1
src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs

@ -8,7 +8,6 @@
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
// ReSharper disable InvertIf
namespace Squidex.Infrastructure.MongoDb

1
src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs

@ -8,7 +8,6 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
// ReSharper disable InvertIf
namespace Squidex.Infrastructure.CQRS.Commands

1
src/Squidex.Infrastructure/CQRS/Events/EventDataFormatter.cs

@ -8,7 +8,6 @@
using System;
using Newtonsoft.Json;
// ReSharper disable InconsistentNaming
namespace Squidex.Infrastructure.CQRS.Events

1
src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs

@ -12,7 +12,6 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NodaTime;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable InvertIf

1
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -9,7 +9,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
// ReSharper disable InvertIf
// ReSharper disable LoopCanBeConvertedToQuery

2
src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs

@ -8,6 +8,7 @@
using System;
using System.Reflection;
// ReSharper disable UnusedMember.Local
namespace Squidex.Infrastructure.Dispatching
{
@ -27,7 +28,6 @@ namespace Squidex.Infrastructure.Dispatching
return new Tuple<Type, Action<TTarget, object, TContext>>(inputType, (Action<TTarget, object, TContext>)handler);
}
// ReSharper disable once UnusedMember.Local
private static Action<TTarget, object, TContext> Factory<TTarget, TIn, TContext>(MethodInfo methodInfo)
{
var type = typeof(Action<TTarget, TIn, TContext>);

2
src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs

@ -8,6 +8,7 @@
using System;
using System.Reflection;
// ReSharper disable UnusedMember.Local
namespace Squidex.Infrastructure.Dispatching
{
@ -27,7 +28,6 @@ namespace Squidex.Infrastructure.Dispatching
return new Tuple<Type, Action<T, object>>(inputType, (Action<T, object>)handler);
}
// ReSharper disable once UnusedMember.Local
private static Action<TTarget, object> Factory<TTarget, TIn>(MethodInfo methodInfo)
{
var type = typeof(Action<TTarget, TIn>);

2
src/Squidex.Infrastructure/Dispatching/FuncContextDispatcherFactory.cs

@ -8,6 +8,7 @@
using System;
using System.Reflection;
// ReSharper disable UnusedMember.Local
namespace Squidex.Infrastructure.Dispatching
{
@ -27,7 +28,6 @@ namespace Squidex.Infrastructure.Dispatching
return new Tuple<Type, Func<TTarget, object, TContext, TOut>>(inputType, (Func<TTarget, object, TContext, TOut>)handler);
}
// ReSharper disable once UnusedMember.Local
private static Func<TTarget, object, TContext, TOut> Factory<TTarget, TIn, TContext, TOut>(MethodInfo methodInfo)
{
var type = typeof(Func<TTarget, TIn, TContext, TOut>);

2
src/Squidex.Infrastructure/Dispatching/FuncDispatcherFactory.cs

@ -8,6 +8,7 @@
using System;
using System.Reflection;
// ReSharper disable once UnusedMember.Local
namespace Squidex.Infrastructure.Dispatching
{
@ -27,7 +28,6 @@ namespace Squidex.Infrastructure.Dispatching
return new Tuple<Type, Func<TTarget, object, TReturn>>(inputType, (Func<TTarget, object, TReturn>)handler);
}
// ReSharper disable once UnusedMember.Local
private static Func<TTarget, object, TReturn> Factory<TTarget, TIn, TReturn>(MethodInfo methodInfo)
{
var type = typeof(Func<TTarget, TIn, TReturn>);

1
src/Squidex.Infrastructure/Guard.cs

@ -12,7 +12,6 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
// ReSharper disable InvertIf
namespace Squidex.Infrastructure

1
src/Squidex.Infrastructure/Reflection/SimpleMapper.cs

@ -10,7 +10,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
// ReSharper disable StaticMemberInGenericType
namespace Squidex.Infrastructure.Reflection

1
src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs

@ -15,7 +15,6 @@ using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Utils;
// ReSharper disable InvertIf
namespace Squidex.Read.Apps.Services.Implementations

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

@ -9,14 +9,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Read.Contents.Repositories
{
public interface IContentRepository
{
Task<List<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, int? take, int? skip, string query);
Task<List<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, string odataQuery, HashSet<Language> languages);
Task<long> CountAsync(Guid schemaId, bool nonPublished, string query);
Task<long> CountAsync(Guid schemaId, bool nonPublished, string odataQuery, HashSet<Language> languages);
Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id);
}

1
src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs

@ -16,7 +16,6 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Utils;
using Squidex.Events;
// ReSharper disable InvertIf
namespace Squidex.Read.Schemas.Services.Implementations

1
src/Squidex.Store.MongoDb/Contents/MongoContentEntity.cs

@ -18,7 +18,6 @@ using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Contents;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable InvertIf

137
src/Squidex.Store.MongoDb/Contents/MongoContentRepository.cs

@ -25,6 +25,7 @@ using Squidex.Infrastructure.Reflection;
using Squidex.Read.Contents;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.Schemas.Services;
using Squidex.Store.MongoDb.Contents.Visitors;
using Squidex.Store.MongoDb.Utils;
namespace Squidex.Store.MongoDb.Contents
@ -34,23 +35,7 @@ namespace Squidex.Store.MongoDb.Contents
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
private readonly ISchemaProvider schemaProvider;
protected ProjectionDefinitionBuilder<MongoContentEntity> Projection
{
get
{
return Builders<MongoContentEntity>.Projection;
}
}
protected SortDefinitionBuilder<MongoContentEntity> Sort
{
get
{
return Builders<MongoContentEntity>.Sort;
}
}
protected UpdateDefinitionBuilder<MongoContentEntity> Update
{
get
@ -59,14 +44,6 @@ namespace Squidex.Store.MongoDb.Contents
}
}
protected FilterDefinitionBuilder<MongoContentEntity> Filter
{
get
{
return Builders<MongoContentEntity>.Filter;
}
}
protected IndexKeysDefinitionBuilder<MongoContentEntity> IndexKeys
{
get
@ -104,96 +81,60 @@ namespace Squidex.Store.MongoDb.Contents
}
}
public async Task<List<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, int? take, int? skip, string query)
public async Task<List<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, string odataQuery, HashSet<Language> languages)
{
var cursor = BuildQuery(schemaId, nonPublished, query);
if (take.HasValue)
{
cursor.Limit(take.Value);
}
List<IContentEntity> result = null;
if (skip.HasValue)
await ForSchemaAsync(schemaId, async (collection, schema) =>
{
cursor.Skip(skip.Value);
}
var parser = schema.ParseQuery(languages, odataQuery);
var cursor = collection.Find(parser, schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schema);
cursor.SortByDescending(x => x.LastModified);
var entities = await cursor.ToListAsync();
var schemaEntity = await schemaProvider.FindSchemaByIdAsync(schemaId);
if (schemaEntity == null)
{
return new List<IContentEntity>();
}
var entities = await cursor.ToListAsync();
foreach (var entity in entities)
{
entity.ParseData(schemaEntity.Schema);
}
return entities.OfType<IContentEntity>().ToList();
}
foreach (var entity in entities)
{
entity.ParseData(schema);
}
public Task<long> CountAsync(Guid schemaId, bool nonPublished, string query)
{
var cursor = BuildQuery(schemaId, nonPublished, query);
result = entities.OfType<IContentEntity>().ToList();
});
return cursor.CountAsync();
return result;
}
private IFindFluent<MongoContentEntity, MongoContentEntity> BuildQuery(Guid schemaId, bool nonPublished, string query)
public async Task<long> CountAsync(Guid schemaId, bool nonPublished, string odataQuery, HashSet<Language> languages)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IsDeleted, false)
};
var result = 0L;
if (!string.IsNullOrWhiteSpace(query))
await ForSchemaAsync(schemaId, async (collection, schema) =>
{
filters.Add(Filter.Text(query, "en"));
}
if (!nonPublished)
{
filters.Add(Filter.Eq(x => x.IsPublished, false));
}
var parser = schema.ParseQuery(languages, odataQuery);
var cursor = collection.Find(parser, schema, nonPublished);
var collection = GetCollection(schemaId);
var cursor = collection.Find(Filter.And(filters));
result = await cursor.CountAsync();
});
return cursor;
return result;
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id)
{
var collection = GetCollection(schemaId);
var entity = await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
MongoContentEntity result = null;
if (entity == null)
await ForSchemaAsync(schemaId, async (collection, schema) =>
{
return null;
}
var schemaEntity = await schemaProvider.FindSchemaByIdAsync(schemaId);
result = await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
if (schemaEntity == null)
{
return null;
}
entity.ParseData(schemaEntity.Schema);
result?.ParseData(schema);
});
return entity;
return result;
}
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(headers, (collection, schema) =>
return ForSchemaAsync(headers.SchemaId(), (collection, schema) =>
{
return collection.CreateAsync(headers, x =>
{
@ -206,7 +147,7 @@ namespace Squidex.Store.MongoDb.Contents
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(headers, (collection, schema) =>
return ForSchemaAsync(headers.SchemaId(), (collection, schema) =>
{
return collection.UpdateAsync(headers, x =>
{
@ -217,7 +158,7 @@ namespace Squidex.Store.MongoDb.Contents
protected Task On(ContentPublished @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(headers, collection =>
return ForSchemaAsync(headers.SchemaId(), collection =>
{
return collection.UpdateAsync(headers, x =>
{
@ -228,7 +169,7 @@ namespace Squidex.Store.MongoDb.Contents
protected Task On(ContentUnpublished @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(headers, collection =>
return ForSchemaAsync(headers.SchemaId(), collection =>
{
return collection.UpdateAsync(headers, x =>
{
@ -239,7 +180,7 @@ namespace Squidex.Store.MongoDb.Contents
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(headers, collection =>
return ForSchemaAsync(headers.SchemaId(), collection =>
{
return collection.UpdateAsync(headers, x =>
{
@ -267,11 +208,11 @@ namespace Squidex.Store.MongoDb.Contents
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
private async Task ForSchemaAsync(EnvelopeHeaders headers, Func<IMongoCollection<MongoContentEntity>, Schema, Task> action)
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Schema, Task> action)
{
var collection = GetCollection(headers.SchemaId());
var collection = GetCollection(schemaId);
var schemaEntity = await schemaProvider.FindSchemaByIdAsync(headers.SchemaId());
var schemaEntity = await schemaProvider.FindSchemaByIdAsync(schemaId);
if (schemaEntity == null)
{
@ -281,9 +222,9 @@ namespace Squidex.Store.MongoDb.Contents
await action(collection, schemaEntity.Schema);
}
private async Task ForSchemaAsync(EnvelopeHeaders headers, Func<IMongoCollection<MongoContentEntity>, Task> action)
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Task> action)
{
var collection = GetCollection(headers.SchemaId());
var collection = GetCollection(schemaId);
await action(collection);
}

32
src/Squidex.Store.MongoDb/Contents/Visitors/ConstantVisitor.cs

@ -0,0 +1,32 @@
// ==========================================================================
// ConstantVisitor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.OData.Core.UriParser.Semantic;
using Microsoft.OData.Core.UriParser.Visitors;
namespace Squidex.Store.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(ConstantNode nodeIn)
{
return nodeIn.Value;
}
}
}

39
src/Squidex.Store.MongoDb/Contents/Visitors/FilterBuilder.cs

@ -0,0 +1,39 @@
// ==========================================================================
// FilterBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.OData.Core.UriParser;
using MongoDB.Driver;
using Squidex.Core.Schemas;
// ReSharper disable ConvertIfStatementToReturnStatement
namespace Squidex.Store.MongoDb.Contents.Visitors
{
public static class FilterBuilder
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
public static FilterDefinition<MongoContentEntity> Build(ODataUriParser query, Schema schema)
{
var search = query.ParseSearch();
if (search != null)
{
return Filter.Text(ConstantVisitor.Visit(search.Expression).ToString());
}
var filter = query.ParseFilter();
if (filter != null)
{
return FilterVisitor.Visit(filter.Expression, schema);
}
return null;
}
}
}

95
src/Squidex.Store.MongoDb/Contents/Visitors/FilterVisitor.cs

@ -0,0 +1,95 @@
// ==========================================================================
// FilterVisitor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Microsoft.OData.Core.UriParser.Semantic;
using Microsoft.OData.Core.UriParser.TreeNodeKinds;
using Microsoft.OData.Core.UriParser.Visitors;
using MongoDB.Driver;
using Squidex.Core.Schemas;
// ReSharper disable SwitchStatementMissingSomeCases
// ReSharper disable ConvertIfStatementToSwitchStatement
namespace Squidex.Store.MongoDb.Contents.Visitors
{
public class FilterVisitor : QueryNodeVisitor<FilterDefinition<MongoContentEntity>>
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
private readonly Schema schema;
private FilterVisitor(Schema schema)
{
this.schema = schema;
}
public static FilterDefinition<MongoContentEntity> Visit(QueryNode node, Schema schema)
{
var visitor = new FilterVisitor(schema);
return node.Accept(visitor);
}
public override FilterDefinition<MongoContentEntity> Visit(UnaryOperatorNode nodeIn)
{
if (nodeIn.OperatorKind == UnaryOperatorKind.Not)
{
return Filter.Not(nodeIn.Operand.Accept(this));
}
throw new NotSupportedException();
}
public override FilterDefinition<MongoContentEntity> 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.OperatorKind == BinaryOperatorKind.NotEqual)
{
return Filter.Ne(BuildFieldDefinition(nodeIn.Left), 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 FieldDefinition<MongoContentEntity, object> BuildFieldDefinition(QueryNode nodeIn)
{
return PropertyVisitor.Visit(nodeIn, schema);
}
private static object BuildValue(QueryNode nodeIn)
{
return ConstantVisitor.Visit(nodeIn);
}
}
}

72
src/Squidex.Store.MongoDb/Contents/Visitors/FindExtensions.cs

@ -0,0 +1,72 @@
// ==========================================================================
// FindExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.OData.Core.UriParser;
using MongoDB.Driver;
using Squidex.Core.Schemas;
namespace Squidex.Store.MongoDb.Contents.Visitors
{
public static class FindExtensions
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
public static IFindFluent<MongoContentEntity, MongoContentEntity> Sort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query, Schema schema)
{
return cursor.Sort(SortBuilder.BuildSort(query, schema));
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Take(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query)
{
var top = query.ParseTop();
if (top.HasValue)
{
cursor = cursor.Limit((int)top.Value);
}
return cursor;
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Skip(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query)
{
var skip = query.ParseSkip();
if (skip.HasValue)
{
cursor = cursor.Skip((int)skip.Value);
}
return cursor;
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Find(this IMongoCollection<MongoContentEntity> cursor, ODataUriParser query, Schema schema, bool nonPublished)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IsDeleted, false)
};
if (!nonPublished)
{
filters.Add(Filter.Eq(x => x.IsPublished, false));
}
var filter = FilterBuilder.Build(query, schema);
if (filter != null)
{
filters.Add(filter);
}
return cursor.Find(Filter.And(filters));
}
}
}

63
src/Squidex.Store.MongoDb/Contents/Visitors/PropertyVisitor.cs

@ -0,0 +1,63 @@
// ==========================================================================
// PropertyVisitor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.OData.Core.UriParser.Semantic;
using Microsoft.OData.Core.UriParser.Visitors;
using MongoDB.Driver;
using Squidex.Core.Schemas;
// ReSharper disable InvertIf
// ReSharper disable RedundantIfElseBlock
namespace Squidex.Store.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)
{
Field field;
if (!schema.FieldsByName.TryGetValue(propertyNames[1], out field))
{
throw new NotSupportedException();
}
propertyNames[1] = field.Id.ToString();
}
var propertyName = string.Join(".", propertyNames);
return new StringFieldDefinition<MongoContentEntity, object>(propertyName);
}
public override ImmutableList<string> Visit(ConvertNode nodeIn)
{
return nodeIn.Source.Accept(this);
}
public override ImmutableList<string> Visit(SingleValuePropertyAccessNode nodeIn)
{
if (nodeIn.Source is SingleValuePropertyAccessNode)
{
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name);
}
else
{
return ImmutableList.Create(nodeIn.Property.Name);
}
}
}
}

58
src/Squidex.Store.MongoDb/Contents/Visitors/SchemaExtensions.cs

@ -0,0 +1,58 @@
// ==========================================================================
// SchemaExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Microsoft.OData.Core.UriParser;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Store.MongoDb.Contents.Visitors
{
public static class SchemaExtensions
{
public static EdmModel BuildEdmModel(this Schema schema, HashSet<Language> languages)
{
var model = new EdmModel();
var container = new EdmEntityContainer("Squidex", "Container");
var schemaType = schema.BuildEdmType(languages, x =>
{
model.AddElement(x);
return x;
});
var entityType = new EdmEntityType("Squidex", schema.Name);
entityType.AddStructuralProperty("Data", new EdmComplexTypeReference(schemaType, false));
entityType.AddStructuralProperty("Created", EdmPrimitiveTypeKind.Date);
entityType.AddStructuralProperty("CreatedBy", EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty("LastModified", EdmPrimitiveTypeKind.Date);
entityType.AddStructuralProperty("LastModifiedBy", EdmPrimitiveTypeKind.String);
model.AddElement(container);
model.AddElement(schemaType);
model.AddElement(entityType);
container.AddEntitySet($"{schema.Name}_Set", entityType);
return model;
}
public static ODataUriParser ParseQuery(this Schema schema, HashSet<Language> languages, string query)
{
var model = schema.BuildEdmModel(languages);
var parser = new ODataUriParser(model, new Uri($"{schema.Name}_Set?{query}", UriKind.Relative));
return parser;
}
}
}

66
src/Squidex.Store.MongoDb/Contents/Visitors/SortBuilder.cs

@ -0,0 +1,66 @@
// ==========================================================================
// SortBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.OData.Core.UriParser;
using Microsoft.OData.Core.UriParser.Semantic;
using MongoDB.Driver;
using Squidex.Core.Schemas;
// ReSharper disable RedundantIfElseBlock
// ReSharper disable ConvertIfStatementToReturnStatement
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
namespace Squidex.Store.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));
}
}
}
}

1
src/Squidex.Write/Apps/AppContributors.cs

@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Linq;
using Squidex.Core.Apps;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.Write.Apps

12
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -12,10 +12,10 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using Squidex.Controllers.Api;
using Squidex.Controllers.ContentApi.Models;
using Squidex.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
@ -42,7 +42,7 @@ namespace Squidex.Controllers.ContentApi
[HttpGet]
[Route("content/{app}/{name}")]
public async Task<IActionResult> GetContents(string name, [FromQuery] string query = null, [FromQuery] int? take = null, [FromQuery] int? skip = null, [FromQuery] bool nonPublished = false, [FromQuery] bool hidden = false)
public async Task<IActionResult> GetContents(string name, [FromQuery] bool nonPublished = false, [FromQuery] bool hidden = false)
{
var schemaEntity = await schemaProvider.FindSchemaByNameAsync(AppId, name);
@ -51,8 +51,12 @@ namespace Squidex.Controllers.ContentApi
return NotFound();
}
var taskForContents = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, take, skip, query);
var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, query);
var languages = new HashSet<Language>(App.Languages);
var query = Request.QueryString.ToString();
var taskForContents = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, query, languages);
var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, query, languages);
await Task.WhenAll(taskForContents, taskForCount);

9
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -25,7 +25,6 @@ using Squidex.Pipeline.Swagger;
using Squidex.Read.Apps;
using Squidex.Read.Schemas;
// ReSharper disable InvertIf
// ReSharper disable SuggestBaseTypeForParameter
// ReSharper disable PrivateFieldCanBeConvertedToLocalVariable
@ -203,10 +202,10 @@ When you change the field to be localizable the value will become the value for
{
operation.Summary = $"Queries {schemaName} content elements.";
operation.Parameters.AddQueryParameter("take", JsonObjectType.Number, "The number of elements to take.");
operation.Parameters.AddQueryParameter("skip", JsonObjectType.Number, "The number of elements to skip.");
operation.Parameters.AddQueryParameter("query", JsonObjectType.String, "Optional full text query skip.");
operation.Parameters.AddQueryParameter("$top", JsonObjectType.Number, "The number of elements to take.");
operation.Parameters.AddQueryParameter("$skip", JsonObjectType.Number, "The number of elements to skip.");
operation.Parameters.AddQueryParameter("$filter", JsonObjectType.String, "Optional filter.");
operation.Parameters.AddQueryParameter("$search", JsonObjectType.String, "Optional full text query skip.");
var responseSchema = CreateContentsSchema(schema.BuildSchema(languages, AppendSchema), schemaName, schema.Name);

Loading…
Cancel
Save