Browse Source

Advanced filtering and bugfixes

pull/1/head
Sebastian 9 years ago
parent
commit
d0cbe850c7
  1. 11
      Squidex.sln
  2. 2
      src/Squidex.Read.MongoDb/Apps/MongoAppClientEntity.cs
  3. 6
      src/Squidex.Read.MongoDb/Apps/MongoAppRepository.cs
  4. 1
      src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
  5. 46
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
  6. 2
      src/Squidex.Read.MongoDb/Contents/Visitors/FilterBuilder.cs
  7. 20
      src/Squidex.Read.MongoDb/Contents/Visitors/FilterVisitor.cs
  8. 10
      src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs
  9. 5
      src/Squidex.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs
  10. 10
      src/Squidex.Read.MongoDb/Contents/Visitors/SchemaExtensions.cs
  11. 44
      src/Squidex.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs
  12. 1
      src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs
  13. 4
      src/Squidex.Read.MongoDb/MongoDbModule.cs
  14. 1
      src/Squidex.Read.MongoDb/MyMongoDbOptions.cs
  15. 2
      src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  16. 2
      src/Squidex.Read.MongoDb/Users/MongoUserRepository.cs
  17. 1
      src/Squidex.Read.MongoDb/Utils/EntityMapper.cs
  18. 6
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  19. 2
      src/Squidex/app/features/content/pages/contents/content-item.component.html
  20. 2
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  21. 4
      src/Squidex/app/features/content/pages/contents/contents-page.component.scss
  22. 21
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  23. 2
      src/Squidex/app/shared/components/app-component-base.ts
  24. 19
      src/Squidex/app/shared/services/contents.service.ts
  25. 9
      tests/RunCoverage.bat
  26. 6
      tests/Squidex.Core.Tests/Contents/ContentDataTests.cs
  27. 272
      tests/Squidex.Read.Tests/MongoDb/Contents/ODataQueryTests.cs
  28. 2
      tests/Squidex.Read.Tests/Squidex.Read.Tests.xproj
  29. 3
      tests/Squidex.Read.Tests/project.json

11
Squidex.sln

@ -28,8 +28,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Read", "src\Squidex
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Read.MongoDb", "src\Squidex.Read.MongoDb\Squidex.Read.MongoDb.xproj", "{28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Read.MongoDb", "src\Squidex.Read.MongoDb\Squidex.Read.MongoDb.xproj", "{28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "squidex_store", "squidex_store", "{4082AA58-D410-4C52-8E88-3B0A4E860B28}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Write.Tests", "tests\Squidex.Write.Tests\Squidex.Write.Tests.xproj", "{9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Write.Tests", "tests\Squidex.Write.Tests\Squidex.Write.Tests.xproj", "{9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.Tests", "tests\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.xproj", "{7FD0A92B-7862-4BB1-932B-B52A9CACB56B}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.Tests", "tests\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.xproj", "{7FD0A92B-7862-4BB1-932B-B52A9CACB56B}"
@ -40,6 +38,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.Mong
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.RabbitMq", "src\Squidex.Infrastructure.RabbitMq\Squidex.Infrastructure.RabbitMq.xproj", "{3C9BA12D-F5F2-4355-8D30-8289E4D0752D}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Infrastructure.RabbitMq", "src\Squidex.Infrastructure.RabbitMq\Squidex.Infrastructure.RabbitMq.xproj", "{3C9BA12D-F5F2-4355-8D30-8289E4D0752D}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Squidex.Read.Tests", "tests\Squidex.Read.Tests\Squidex.Read.Tests.xproj", "{8B074219-F69A-4E41-83C6-12EE1E647779}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -94,6 +94,10 @@ Global
{3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Release|Any CPU.Build.0 = Release|Any CPU {3C9BA12D-F5F2-4355-8D30-8289E4D0752D}.Release|Any CPU.Build.0 = Release|Any CPU
{8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -105,11 +109,12 @@ Global
{25F66C64-058A-4D44-BC0C-F12A054F9A91} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {25F66C64-058A-4D44-BC0C-F12A054F9A91} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{A85201C6-6AF8-4B63-8365-08F741050438} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {A85201C6-6AF8-4B63-8365-08F741050438} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{A92B4734-2587-4F6F-97A3-741BE48709A5} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {A92B4734-2587-4F6F-97A3-741BE48709A5} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{28F8E9E2-FE24-41F7-A888-9FC244A9E2DD} = {4082AA58-D410-4C52-8E88-3B0A4E860B28} {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{9A3DEA7E-1681-4D48-AC5C-1F0DE421A203} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{3C9BA12D-F5F2-4355-8D30-8289E4D0752D} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {3C9BA12D-F5F2-4355-8D30-8289E4D0752D} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{8B074219-F69A-4E41-83C6-12EE1E647779} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

2
src/Squidex.Read.MongoDb/Apps/MongoAppClientEntity.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// MongoAppClientKeyEntity.cs // MongoAppClientEntity.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group

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

@ -11,16 +11,16 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Events.Apps; using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Replay;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Read.Apps; using Squidex.Read.Apps;
using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Repositories;
using Squidex.Read.MongoDb.Utils; using Squidex.Read.MongoDb.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Replay;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Read.MongoDb.Apps namespace Squidex.Read.MongoDb.Apps
{ {

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

@ -101,6 +101,7 @@ namespace Squidex.Read.MongoDb.Contents
{ {
if (text.Type == JTokenType.String) if (text.Type == JTokenType.String)
{ {
stringBuilder.Append(" ");
stringBuilder.Append(text); stringBuilder.Append(text);
} }
} }

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

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.OData.Core;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
@ -24,9 +25,9 @@ using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Read.Contents; using Squidex.Read.Contents;
using Squidex.Read.Contents.Repositories; using Squidex.Read.Contents.Repositories;
using Squidex.Read.Schemas.Services;
using Squidex.Read.MongoDb.Contents.Visitors; using Squidex.Read.MongoDb.Contents.Visitors;
using Squidex.Read.MongoDb.Utils; using Squidex.Read.MongoDb.Utils;
using Squidex.Read.Schemas.Services;
namespace Squidex.Read.MongoDb.Contents namespace Squidex.Read.MongoDb.Contents
{ {
@ -86,9 +87,26 @@ namespace Squidex.Read.MongoDb.Contents
List<IContentEntity> result = null; List<IContentEntity> result = null;
await ForSchemaAsync(schemaId, async (collection, schema) => await ForSchemaAsync(schemaId, async (collection, schema) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{ {
var parser = schema.ParseQuery(languages, odataQuery); var parser = schema.ParseQuery(languages, odataQuery);
var cursor = collection.Find(parser, schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schema);
cursor = collection.Find(parser, schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schema);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (ODataException e)
{
throw new ValidationException("Failed to parse query: " + e.Message, e);
}
var entities = await cursor.ToListAsync(); var entities = await cursor.ToListAsync();
@ -108,9 +126,26 @@ namespace Squidex.Read.MongoDb.Contents
var result = 0L; var result = 0L;
await ForSchemaAsync(schemaId, async (collection, schema) => await ForSchemaAsync(schemaId, async (collection, schema) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{ {
var parser = schema.ParseQuery(languages, odataQuery); var parser = schema.ParseQuery(languages, odataQuery);
var cursor = collection.Find(parser, schema, nonPublished);
cursor = collection.Find(parser, schema, nonPublished);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (ODataException e)
{
throw new ValidationException("Failed to parse query: " + e.Message, e);
}
result = await cursor.CountAsync(); result = await cursor.CountAsync();
}); });
@ -196,11 +231,12 @@ namespace Squidex.Read.MongoDb.Contents
return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition<MongoContentEntity>($"Data.{@event.FieldId}"))); return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition<MongoContentEntity>($"Data.{@event.FieldId}")));
} }
protected Task On(SchemaCreated @event, EnvelopeHeaders headers) protected async Task On(SchemaCreated @event, EnvelopeHeaders headers)
{ {
var collection = GetCollection(headers.AggregateId()); var collection = GetCollection(headers.AggregateId());
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished).Text(x => x.Text)); await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished));
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.Text));
} }
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)

2
src/Squidex.Read.MongoDb/Contents/Visitors/FilterBuilder.cs

@ -23,7 +23,7 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
if (search != null) if (search != null)
{ {
return Filter.Text(ConstantVisitor.Visit(search.Expression).ToString()); return Filter.Text(SearchTermVisitor.Visit(search.Expression).ToString());
} }
var filter = query.ParseFilter(); var filter = query.ParseFilter();

20
src/Squidex.Read.MongoDb/Contents/Visitors/FilterVisitor.cs

@ -7,9 +7,11 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using Microsoft.OData.Core.UriParser.Semantic; using Microsoft.OData.Core.UriParser.Semantic;
using Microsoft.OData.Core.UriParser.TreeNodeKinds; using Microsoft.OData.Core.UriParser.TreeNodeKinds;
using Microsoft.OData.Core.UriParser.Visitors; using Microsoft.OData.Core.UriParser.Visitors;
using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
// ReSharper disable SwitchStatementMissingSomeCases // ReSharper disable SwitchStatementMissingSomeCases
@ -44,6 +46,24 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override FilterDefinition<MongoContentEntity> Visit(SingleValueFunctionCallNode nodeIn)
{
if (nodeIn.Name == "endswith")
{
return Filter.Regex(BuildFieldDefinition(nodeIn.Parameters.ElementAt(0)), new BsonRegularExpression(BuildValue(nodeIn.Parameters.ElementAt(1)) + "$", "i"));
}
if (nodeIn.Name == "startswith")
{
return Filter.Regex(BuildFieldDefinition(nodeIn.Parameters.ElementAt(0)), new BsonRegularExpression("^" + BuildValue(nodeIn.Parameters.ElementAt(1)), "i"));
}
if (nodeIn.Name == "contains")
{
return Filter.Regex(BuildFieldDefinition(nodeIn.Parameters.ElementAt(0)), new BsonRegularExpression(BuildValue(nodeIn.Parameters.ElementAt(1)).ToString(), "i"));
}
throw new NotSupportedException();
}
public override FilterDefinition<MongoContentEntity> Visit(BinaryOperatorNode nodeIn) public override FilterDefinition<MongoContentEntity> Visit(BinaryOperatorNode nodeIn)
{ {
if (nodeIn.OperatorKind == BinaryOperatorKind.And) if (nodeIn.OperatorKind == BinaryOperatorKind.And)

10
src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs

@ -19,7 +19,6 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
public static IFindFluent<MongoContentEntity, MongoContentEntity> Sort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query, Schema schema) public static IFindFluent<MongoContentEntity, MongoContentEntity> Sort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ODataUriParser query, Schema schema)
{ {
return cursor.Sort(SortBuilder.BuildSort(query, schema)); return cursor.Sort(SortBuilder.BuildSort(query, schema));
} }
@ -48,6 +47,13 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
} }
public static IFindFluent<MongoContentEntity, MongoContentEntity> Find(this IMongoCollection<MongoContentEntity> cursor, ODataUriParser query, Schema schema, bool nonPublished) public static IFindFluent<MongoContentEntity, MongoContentEntity> Find(this IMongoCollection<MongoContentEntity> cursor, ODataUriParser query, Schema schema, bool nonPublished)
{
var filter = BuildQuery(query, schema, nonPublished);
return cursor.Find(filter);
}
public static FilterDefinition<MongoContentEntity> BuildQuery(ODataUriParser query, Schema schema, bool nonPublished)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
@ -66,7 +72,7 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
filters.Add(filter); filters.Add(filter);
} }
return cursor.Find(Filter.And(filters)); return Filter.And(filters);
} }
} }
} }

5
src/Squidex.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs

@ -38,6 +38,11 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
propertyNames[1] = field.Id.ToString(); propertyNames[1] = field.Id.ToString();
} }
if (char.IsLower(propertyNames[0][0]))
{
propertyNames[0] = char.ToUpperInvariant(propertyNames[0][0]) + propertyNames[0].Substring(1);
}
var propertyName = string.Join(".", propertyNames); var propertyName = string.Join(".", propertyNames);
return new StringFieldDefinition<MongoContentEntity, object>(propertyName); return new StringFieldDefinition<MongoContentEntity, object>(propertyName);

10
src/Squidex.Read.MongoDb/Contents/Visitors/SchemaExtensions.cs

@ -31,11 +31,11 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
}); });
var entityType = new EdmEntityType("Squidex", schema.Name); var entityType = new EdmEntityType("Squidex", schema.Name);
entityType.AddStructuralProperty("Data", new EdmComplexTypeReference(schemaType, false)); entityType.AddStructuralProperty("data", new EdmComplexTypeReference(schemaType, false));
entityType.AddStructuralProperty("Created", EdmPrimitiveTypeKind.Date); entityType.AddStructuralProperty("created", EdmPrimitiveTypeKind.Date);
entityType.AddStructuralProperty("CreatedBy", EdmPrimitiveTypeKind.String); entityType.AddStructuralProperty("createdBy", EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty("LastModified", EdmPrimitiveTypeKind.Date); entityType.AddStructuralProperty("lastModified", EdmPrimitiveTypeKind.Date);
entityType.AddStructuralProperty("LastModifiedBy", EdmPrimitiveTypeKind.String); entityType.AddStructuralProperty("lastModifiedBy", EdmPrimitiveTypeKind.String);
model.AddElement(container); model.AddElement(container);
model.AddElement(schemaType); model.AddElement(schemaType);

44
src/Squidex.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs

@ -0,0 +1,44 @@
// ==========================================================================
// SearchTermVisitor.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;
namespace Squidex.Read.MongoDb.Contents.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;
}
}
}

1
src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs

@ -12,7 +12,6 @@ using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Read;
namespace Squidex.Read.MongoDb.History namespace Squidex.Read.MongoDb.History
{ {

4
src/Squidex.Read.MongoDb/MongoDbModule.cs

@ -18,14 +18,14 @@ using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Repositories;
using Squidex.Read.Contents.Repositories; using Squidex.Read.Contents.Repositories;
using Squidex.Read.History.Repositories; using Squidex.Read.History.Repositories;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Users.Repositories;
using Squidex.Read.MongoDb.Apps; using Squidex.Read.MongoDb.Apps;
using Squidex.Read.MongoDb.Contents; using Squidex.Read.MongoDb.Contents;
using Squidex.Read.MongoDb.History; using Squidex.Read.MongoDb.History;
using Squidex.Read.MongoDb.Infrastructure; using Squidex.Read.MongoDb.Infrastructure;
using Squidex.Read.MongoDb.Schemas; using Squidex.Read.MongoDb.Schemas;
using Squidex.Read.MongoDb.Users; using Squidex.Read.MongoDb.Users;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Users.Repositories;
namespace Squidex.Read.MongoDb namespace Squidex.Read.MongoDb
{ {

1
src/Squidex.Read.MongoDb/MyMongoDbOptions.cs

@ -5,6 +5,7 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Read.MongoDb namespace Squidex.Read.MongoDb
{ {
public class MyMongoDbOptions public class MyMongoDbOptions

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

@ -22,9 +22,9 @@ using Squidex.Infrastructure.CQRS.Replay;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Read.MongoDb.Utils;
using Squidex.Read.Schemas; using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Read.MongoDb.Utils;
namespace Squidex.Read.MongoDb.Schemas namespace Squidex.Read.MongoDb.Schemas
{ {

2
src/Squidex.Read.MongoDb/Users/MongoUserRepository.cs

@ -6,8 +6,8 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.AspNetCore.Identity.MongoDB;

1
src/Squidex.Read.MongoDb/Utils/EntityMapper.cs

@ -12,7 +12,6 @@ using MongoDB.Driver;
using Squidex.Events; using Squidex.Events;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Read;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression // ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable SuspiciousTypeConversion.Global // ReSharper disable SuspiciousTypeConversion.Global

6
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -46,7 +46,7 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
public contentData: any = null; public contentData: any = null;
public contentId: string; public contentId: string;
public isNewMode = false; public isNewMode = true;
public languages: AppLanguageDto[] = []; public languages: AppLanguageDto[] = [];
@ -180,12 +180,12 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
if (!content) { if (!content) {
this.contentData = undefined; this.contentData = undefined;
this.contentId = undefined; this.contentId = undefined;
this.isNewMode = false; this.isNewMode = true;
return; return;
} else { } else {
this.contentData = content.data; this.contentData = content.data;
this.contentId = content.id; this.contentId = content.id;
this.isNewMode = true; this.isNewMode = false;
} }
for (const field of this.schema.fields) { for (const field of this.schema.fields) {

2
src/Squidex/app/features/content/pages/contents/content-item.component.html

@ -8,7 +8,7 @@
<span class="item-modified">{{content.lastModified | fromNow}}</span> <span class="item-modified">{{content.lastModified | fromNow}}</span>
</td> </td>
<td> <td>
<img class="user-picture" [attr.title]="userName(content.lastModifiedBy) | async" [attr.src]="userPicture(content.lastModifiedBy, true) | async" /> <img class="user-picture" [attr.title]="userName(content.lastModifiedBy, true) | async" [attr.src]="userPicture(content.lastModifiedBy, true) | async" />
</td> </td>
<td> <td>
<div class="dropdown dropdown-options" *ngIf="content"> <div class="dropdown dropdown-options" *ngIf="content">

2
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -4,7 +4,7 @@
<div class="panel-header"> <div class="panel-header">
<div class="panel-title-row"> <div class="panel-title-row">
<div class="float-xs-right"> <div class="float-xs-right">
<form class="form-inline"> <form class="form-inline" (ngSubmit)="search()">
<input class="form-control" [formControl]="contentsFilter" placeholder="Search for content..." /> <input class="form-control" [formControl]="contentsFilter" placeholder="Search for content..." />
</form> </form>

4
src/Squidex/app/features/content/pages/contents/contents-page.component.scss

@ -13,6 +13,10 @@
margin-left: 1rem; margin-left: 1rem;
} }
.form-control {
width: 15rem;
}
.form-inline { .form-inline {
display: inline-block; display: inline-block;
} }

21
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -84,27 +84,20 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
public ngOnInit() { public ngOnInit() {
this.messageCreatedSubscription = this.messageCreatedSubscription =
this.messageBus.of(ContentCreated).subscribe(message => { this.messageBus.of(ContentCreated).subscribe(message => {
this.itemLast++;
this.contentTotal++; this.contentTotal++;
this.contentItems = this.contentItems.pushFront(this.createContent(message.id, message.data)); this.contentItems = this.contentItems.pushFront(this.createContent(message.id, message.data));
}); });
this.messageUpdatedSubscription = this.messageUpdatedSubscription =
this.messageBus.of(ContentUpdated).subscribe(message => { this.messageBus.of(ContentUpdated).subscribe(message => {
this.updateContents(message.id, true, message.data); this.updateContents(message.id, undefined, message.data);
}); });
this.route.data.map(p => p['appLanguages']).subscribe((languages: AppLanguageDto[]) => { this.route.data.map(p => p['appLanguages']).subscribe((languages: AppLanguageDto[]) => {
this.languages = languages; this.languages = languages;
}); });
this.contentsFilter.valueChanges.debounceTime(300).subscribe(q => {
this.currentQuery = q;
if (this.schema) {
this.load();
}
});
this.route.data.map(p => p['schema']).subscribe(schema => { this.route.data.map(p => p['schema']).subscribe(schema => {
this.schema = schema; this.schema = schema;
@ -113,6 +106,12 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
}); });
} }
public search() {
this.currentQuery = this.contentsFilter.value;
this.load();
}
public publishContent(content: ContentDto) { public publishContent(content: ContentDto) {
this.appName() this.appName()
.switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id)) .switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id))
@ -204,8 +203,8 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.canGoPrev = this.currentPage > 0; this.canGoPrev = this.currentPage > 0;
} }
private updateContents(id: string, isPublished: boolean, data: any) { private updateContents(id: string, p: boolean | undefined, data: any) {
this.contentItems = this.contentItems.replaceAll(x => x.id === id, c => this.updateContent(c, isPublished, data)); this.contentItems = this.contentItems.replaceAll(x => x.id === id, c => this.updateContent(c, p === undefined ? c.isPublished : p, data));
} }
private createContent(id: string, data: any): ContentDto { private createContent(id: string, data: any): ContentDto {

2
src/Squidex/app/shared/components/app-component-base.ts

@ -24,7 +24,7 @@ export abstract class AppComponentBase {
} }
public appName(): Observable<string> { public appName(): Observable<string> {
return this.appsStore.selectedApp.map(a => a!.name); return this.appsStore.selectedApp.map(a => a!.name).take(1);
} }
public userEmail(userId: string, isRef: boolean = false): Observable<string> { public userEmail(userId: string, isRef: boolean = false): Observable<string> {

19
src/Squidex/app/shared/services/contents.service.ts

@ -48,7 +48,24 @@ export class ContentsService {
} }
public getContents(appName: string, schemaName: string, take: number, skip: number, query: string): Observable<ContentsDto> { public getContents(appName: string, schemaName: string, take: number, skip: number, query: string): Observable<ContentsDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?take=${take}&skip=${skip}&query=${query}&nonPublished=true&hidden=true`); let fullQuery = query ? query.trim() : '';
if (fullQuery.indexOf('$filter') < 0 &&
fullQuery.indexOf('$search') < 0 &&
fullQuery.indexOf('$orderby') < 0 &&
fullQuery.length > 0) {
fullQuery = `$search=${fullQuery}`;
}
if (take > 0) {
fullQuery += `&$top=${take}`;
}
if (skip > 0) {
fullQuery += `&$skip=${skip}`;
}
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?nonPublished=true&hidden=true&${fullQuery}`);
return this.authService.authGet(url) return this.authService.authGet(url)
.map(response => response.json()) .map(response => response.json())

9
tests/RunCoverage.bat

@ -42,6 +42,15 @@ exit /b %errorlevel%
-skipautoprops ^ -skipautoprops ^
-output:"%~dp0\GeneratedReports\Write.xml" ^ -output:"%~dp0\GeneratedReports\Write.xml" ^
-oldStyle -oldStyle
"%UserProfile%\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ^
-register:user ^
-target:"C:\Program Files\dotnet\dotnet.exe" ^
-targetargs:"test %~dp0\Squidex.Read.Tests" ^
-filter:"+[Squidex*]*" ^
-skipautoprops ^
-output:"%~dp0\GeneratedReports\Read.xml" ^
-oldStyle
exit /b %errorlevel% exit /b %errorlevel%
:RunReportGeneratorOutput :RunReportGeneratorOutput

6
tests/Squidex.Core.Tests/Contents/ContentDataTests.cs

@ -39,6 +39,9 @@ namespace Squidex.Core.Contents
.AddValue("en", "en_string") .AddValue("en", "en_string")
.AddValue("de", "de_string")) .AddValue("de", "de_string"))
.AddField("field2", .AddField("field2",
new ContentFieldData()
.AddValue("iv", 3))
.AddField("invalid",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 3)); .AddValue("iv", 3));
@ -67,6 +70,9 @@ namespace Squidex.Core.Contents
.AddValue("en", "en_string") .AddValue("en", "en_string")
.AddValue("de", "de_string")) .AddValue("de", "de_string"))
.AddField("2", .AddField("2",
new ContentFieldData()
.AddValue("iv", 3))
.AddField("99",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 3)); .AddValue("iv", 3));

272
tests/Squidex.Read.Tests/MongoDb/Contents/ODataQueryTests.cs

@ -0,0 +1,272 @@
// ==========================================================================
// ODataQueryTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using Moq;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Read.MongoDb.Contents;
using Squidex.Read.MongoDb.Contents.Visitors;
using Xunit;
// ReSharper disable SpecifyACultureInStringConversionExplicitly
namespace Squidex.Write.MongoDb.Contents
{
public class ODataQueryTests
{
private readonly Schema schema =
Schema.Create("user", new SchemaProperties { Hints = "The User" })
.AddOrUpdateField(new StringField(1, "firstName",
new StringFieldProperties { Label = "FirstName", IsLocalizable = true, IsRequired = true, AllowedValues = new[] { "1", "2" }.ToImmutableList() }))
.AddOrUpdateField(new StringField(2, "lastName",
new StringFieldProperties { Hints = "Last Name" }))
.AddOrUpdateField(new BooleanField(3, "admin",
new BooleanFieldProperties()))
.AddOrUpdateField(new NumberField(4, "age",
new NumberFieldProperties { MinValue = 1, MaxValue = 10 }));
private readonly IBsonSerializerRegistry registry = BsonSerializer.SerializerRegistry;
private readonly IBsonSerializer<MongoContentEntity> serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>();
private readonly HashSet<Language> languages = new HashSet<Language>
{
Language.GetLanguage("en"),
Language.GetLanguage("de")
};
[Fact]
public void Should_parse_query()
{
var parser = schema.ParseQuery(languages, "$filter=data/FirstName/de eq 'Sebastian'");
Assert.NotNull(parser);
}
[Fact]
public void Should_create_not_operator()
{
var i = F("$filter=not endswith(data/firstName/de, 'Sebastian')");
var o = C("{ 'Data.1.de' : { '$not' : /Sebastian$/i } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_starts_with_query()
{
var i = F("$filter=startswith(data/firstName/de, 'Sebastian')");
var o = C("{ 'Data.1.de' : /^Sebastian/i }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_ends_with_query()
{
var i = F("$filter=endswith(data/firstName/de, 'Sebastian')");
var o = C("{ 'Data.1.de' : /Sebastian$/i }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_contains_query()
{
var i = F("$filter=contains(data/firstName/de, 'Sebastian')");
var o = C("{ 'Data.1.de' : /Sebastian/i }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_string_equals_query()
{
var i = F("$filter=data/firstName/de eq 'Sebastian'");
var o = C("{ 'Data.1.de' : 'Sebastian' }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_string_not_equals_query()
{
var i = F("$filter=data/firstName/de ne 'Sebastian'");
var o = C("{ 'Data.1.de' : { '$ne' : 'Sebastian' } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_number_less_than_query()
{
var i = F("$filter=data/age/iv lt 1");
var o = C("{ 'Data.4.iv' : { '$lt' : 1.0 } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_number_less_equals_query()
{
var i = F("$filter=data/age/iv le 1");
var o = C("{ 'Data.4.iv' : { '$lte' : 1.0 } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_number_greater_than_query()
{
var i = F("$filter=data/age/iv gt 1");
var o = C("{ 'Data.4.iv' : { '$gt' : 1.0 } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_number_greater_equals_query()
{
var i = F("$filter=data/age/iv ge 1");
var o = C("{ 'Data.4.iv' : { '$gte' : 1.0 } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_and_query()
{
var i = F("$filter=data/age/iv eq 1 and data/age/iv eq 2");
var o = C("{ '$and' : [{ 'Data.4.iv' : 1.0 }, { 'Data.4.iv' : 2.0 }] }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_or_query()
{
var i = F("$filter=data/age/iv eq 1 or data/age/iv eq 2");
var o = C("{ '$or' : [{ 'Data.4.iv' : 1.0 }, { 'Data.4.iv' : 2.0 }] }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_full_text_query()
{
var i = F("$search=Hello my World");
var o = C("{ '$text' : { '$search' : 'Hello my World' } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_create_full_text_query_with_and()
{
var i = F("$search=A and B");
var o = C("{ '$text' : { '$search' : 'A and B' } }");
Assert.Equal(o, i);
}
[Fact]
public void Should_convert_orderby_with_single_statements()
{
var i = S("$orderby=data/age/iv desc");
var o = C("{ 'Data.4.iv' : -1 }");
Assert.Equal(o, i);
}
[Fact]
public void Should_convert_orderby_with_multiple_statements()
{
var i = S("$orderby=data/age/iv, data/firstName/en desc");
var o = C("{ 'Data.4.iv' : 1, 'Data.1.en' : -1 }");
Assert.Equal(o, i);
}
[Fact]
public void Should_set_top()
{
var parser = schema.ParseQuery(languages, "$top=3");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
cursor.Verify(x => x.Limit(3));
}
[Fact]
public void Should_not_set_top()
{
var parser = schema.ParseQuery(languages, "");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
cursor.Verify(x => x.Limit(It.IsAny<int>()), Times.Never);
}
[Fact]
public void Should_set_skip()
{
var parser = schema.ParseQuery(languages, "$skip=3");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Skip(parser);
cursor.Verify(x => x.Skip(3));
}
[Fact]
public void Should_not_set_skip()
{
var parser = schema.ParseQuery(languages, "");
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Object.Take(parser);
cursor.Verify(x => x.Skip(It.IsAny<int>()), Times.Never);
}
private static string C(string value)
{
return value.Replace('\'', '"');
}
private string S(string value)
{
var parser = schema.ParseQuery(languages, value);
var cursor = new Mock<IFindFluent<MongoContentEntity, MongoContentEntity>>();
var i = string.Empty;
cursor.Setup(x => x.Sort(It.IsAny<SortDefinition<MongoContentEntity>>())).Callback(new Action<SortDefinition<MongoContentEntity>>(s =>
{
i = s.Render(serializer, registry).ToString();
}));
cursor.Object.Sort(parser, schema);
return i;
}
private string F(string value)
{
var parser = schema.ParseQuery(languages, value);
var query = FilterBuilder.Build(parser, schema).Render(serializer, registry).ToString();
return query;
}
}
}

2
tests/Squidex.Read.Tests/Squidex.Read.Tests.xproj

@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>9a3dea7e-1681-4d48-ac5c-1f0de421a203</ProjectGuid> <ProjectGuid>8b074219-f69a-4e41-83c6-12ee1e647779</ProjectGuid>
<RootNamespace>Squidex.Write</RootNamespace> <RootNamespace>Squidex.Write</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>

3
tests/Squidex.Read.Tests/project.json

@ -12,7 +12,8 @@
"Moq": "4.6.38-alpha", "Moq": "4.6.38-alpha",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"Squidex.Write": "1.0.0-*", "Squidex.Read": "1.0.0-*",
"Squidex.Read.MongoDb": "1.0.0-*",
"xunit": "2.2.0-beta4-build3444" "xunit": "2.2.0-beta4-build3444"
}, },
"frameworks": { "frameworks": {

Loading…
Cancel
Save