new search index extension ability
This commit is contained in:
@@ -1,78 +1,22 @@
|
||||
using Raven.Client.Documents.Indexes;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using zero.Core.Entities;
|
||||
using Raven.Client.Documents;
|
||||
using zero.Core.Options;
|
||||
|
||||
namespace zero.Core.Database.Indexes
|
||||
{
|
||||
public class Backoffice_Search : ZeroMultiMapIndex
|
||||
public class Backoffice_Search : ZeroJavascriptIndex
|
||||
{
|
||||
public class Result
|
||||
public override void Setup(IZeroOptions options, IDocumentStore store)
|
||||
{
|
||||
public string Id { get; set; }
|
||||
// TODO index.Conventions is null, but needed for collection name retrieval
|
||||
|
||||
public string Group { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
public List<string> Fields { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
protected override void Create()
|
||||
{
|
||||
AddEntities();
|
||||
Index(nameof(Result.Name), FieldIndexing.Search);
|
||||
Index(nameof(Result.Fields), FieldIndexing.Search);
|
||||
}
|
||||
|
||||
|
||||
protected virtual void AddEntities()
|
||||
{
|
||||
AddPages();
|
||||
AddMediaFolders();
|
||||
//AddMedia();
|
||||
}
|
||||
|
||||
|
||||
protected virtual void AddPages()
|
||||
{
|
||||
AddMap<Page>(map => map.Select(page => new Result()
|
||||
foreach (var map in options.Search.GetAllItems())
|
||||
{
|
||||
Id = page.Id,
|
||||
Group = "zero.page",
|
||||
Name = page.Name,
|
||||
IsActive = page.IsActive,
|
||||
Fields = new()
|
||||
}));
|
||||
}
|
||||
Maps.Add(map.BuildInstruction(this, store));
|
||||
}
|
||||
|
||||
|
||||
protected virtual void AddMediaFolders()
|
||||
{
|
||||
AddMap<MediaFolder>(map => map.Select(page => new Result()
|
||||
{
|
||||
Id = page.Id,
|
||||
Group = "zero.mediafolder",
|
||||
Name = page.Name,
|
||||
IsActive = page.IsActive,
|
||||
Fields = new()
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
protected virtual void AddMedia()
|
||||
{
|
||||
AddMap<Media>(map => map.Select(page => new Result()
|
||||
{
|
||||
Id = page.Id,
|
||||
Group = "zero.media",
|
||||
Name = page.Name,
|
||||
IsActive = page.IsActive,
|
||||
Fields = new()
|
||||
}));
|
||||
//Index(nameof(SearchIndexResult.Name), FieldIndexing.Search);
|
||||
//Index(nameof(SearchIndexResult.Fields), FieldIndexing.Search);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Raven.Client.Documents.Indexes;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Indexes;
|
||||
using Raven.Client.Documents.Indexes.Spatial;
|
||||
using Raven.Client.Documents.Operations.Attachments;
|
||||
using System;
|
||||
@@ -12,7 +13,9 @@ namespace zero.Core.Database
|
||||
public abstract class ZeroJavascriptIndex : AbstractJavaScriptIndexCreationTask, IZeroIndexDefinition
|
||||
{
|
||||
public ZeroJavascriptIndex() { Create(); }
|
||||
protected abstract void Create();
|
||||
protected virtual void Create() { }
|
||||
|
||||
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
|
||||
|
||||
public new string Reduce { get => base.Reduce; set => base.Reduce = value; }
|
||||
public new string OutputReduceToCollection { get => base.OutputReduceToCollection; set => base.OutputReduceToCollection = value; }
|
||||
@@ -47,7 +50,9 @@ namespace zero.Core.Database
|
||||
public abstract class ZeroMultiMapIndex : AbstractMultiMapIndexCreationTask<object>, IZeroIndexDefinition
|
||||
{
|
||||
public ZeroMultiMapIndex() { Create(); }
|
||||
protected abstract void Create();
|
||||
protected virtual void Create() { }
|
||||
|
||||
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
|
||||
|
||||
// AbstractMultiMapIndexCreationTask<TReduceResult>
|
||||
public new void AddMap<TSource>(Expression<Func<IEnumerable<TSource>, IEnumerable>> map) => base.AddMap(map);
|
||||
@@ -112,7 +117,9 @@ namespace zero.Core.Database
|
||||
public abstract class ZeroMultiMapIndex<TReduceResult> : AbstractMultiMapIndexCreationTask<TReduceResult>, IZeroIndexDefinition
|
||||
{
|
||||
public ZeroMultiMapIndex() { Create(); }
|
||||
protected abstract void Create();
|
||||
protected virtual void Create() { }
|
||||
|
||||
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
|
||||
|
||||
// AbstractMultiMapIndexCreationTask<TReduceResult>
|
||||
public new void AddMap<TSource>(Expression<Func<IEnumerable<TSource>, IEnumerable>> map) => base.AddMap(map);
|
||||
@@ -177,7 +184,9 @@ namespace zero.Core.Database
|
||||
public abstract class ZeroIndex<TDocument, TReduceResult> : AbstractIndexCreationTask<TDocument, TReduceResult>, IZeroIndexDefinition
|
||||
{
|
||||
public ZeroIndex() { Create(); }
|
||||
protected abstract void Create();
|
||||
protected virtual void Create() { }
|
||||
|
||||
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
|
||||
|
||||
// AbstractIndexCreationTask<TDocument, TReduceResult>
|
||||
public new Expression<Func<IEnumerable<TDocument>, IEnumerable>> Map { get => base.Map; set => base.Map = value; }
|
||||
@@ -246,7 +255,9 @@ namespace zero.Core.Database
|
||||
public abstract class ZeroIndex : AbstractIndexCreationTask, IZeroIndexDefinition
|
||||
{
|
||||
public ZeroIndex() { Create(); }
|
||||
protected abstract void Create();
|
||||
protected virtual void Create() { }
|
||||
|
||||
public virtual void Setup(IZeroOptions options, IDocumentStore store) { }
|
||||
|
||||
// AbstractIndexCreationTask
|
||||
public new IJsonObject AsJson(object doc) => base.AsJson(doc);
|
||||
@@ -276,15 +287,6 @@ namespace zero.Core.Database
|
||||
|
||||
public interface IZeroIndexDefinition : IAbstractIndexCreationTask
|
||||
{
|
||||
void Setup(IZeroOptions options)
|
||||
{
|
||||
IEnumerable<RavenIndexModifiersOptions.Modifier> modifiers = options.Raven.Indexes.Modifiers.GetAllForType(this.GetType());
|
||||
|
||||
foreach (RavenIndexModifiersOptions.Modifier modifier in modifiers)
|
||||
{
|
||||
Action<IZeroIndexDefinition> action = modifier.Modify.Compile();
|
||||
action.Invoke(this);
|
||||
}
|
||||
}
|
||||
void Setup(IZeroOptions options, IDocumentStore store);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace zero.Core.Models
|
||||
{
|
||||
public class SearchResult
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Icon { get; set; }
|
||||
|
||||
public string Image { get; set; }
|
||||
|
||||
public string Url { get; set; }
|
||||
|
||||
public string Group { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class SearchIndexResult
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Group { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
public List<string> Fields { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Options;
|
||||
|
||||
namespace zero.Core.Extensions
|
||||
{
|
||||
public static class ZeroIndexExtensions
|
||||
{
|
||||
internal static void RunModifiers<T>(this T index, IZeroOptions options) where T : IZeroIndexDefinition
|
||||
{
|
||||
IEnumerable<RavenIndexModifiersOptions.Modifier> modifiers = options.Raven.Indexes.Modifiers.GetAllForType(index.GetType());
|
||||
|
||||
foreach (RavenIndexModifiersOptions.Modifier modifier in modifiers)
|
||||
{
|
||||
Action<IZeroIndexDefinition> action = modifier.Modify.Compile();
|
||||
action.Invoke(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using Raven.Client.Documents.Indexes;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Indexes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Extensions;
|
||||
|
||||
namespace zero.Core.Options
|
||||
{
|
||||
@@ -19,45 +21,71 @@ namespace zero.Core.Options
|
||||
}
|
||||
|
||||
|
||||
public class RavenIndexesOptions : ZeroBackofficeCollection<Type>, IZeroCollectionOptions
|
||||
public class RavenIndexesOptions : ZeroBackofficeCollection<RavenIndexesOptions.Map>, IZeroCollectionOptions
|
||||
{
|
||||
public class Map
|
||||
{
|
||||
internal Type Type { get; set; }
|
||||
|
||||
internal Expression<Func<IZeroIndexDefinition>> CreateIndex { get; set; }
|
||||
|
||||
internal Map(Type type, Expression<Func<IZeroIndexDefinition>> create)
|
||||
{
|
||||
Type = type;
|
||||
CreateIndex = create;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public RavenIndexModifiersOptions Modifiers { get; private set; } = new();
|
||||
|
||||
public void Add<T>() where T : IZeroIndexDefinition, new()
|
||||
{
|
||||
Items.Add(typeof(T));
|
||||
Items.Add(new(typeof(T), () => new T()));
|
||||
}
|
||||
|
||||
public void Add(Type indexType)
|
||||
{
|
||||
Items.Add(indexType);
|
||||
Items.Add(new(indexType, () => (IZeroIndexDefinition)Activator.CreateInstance(indexType)));
|
||||
}
|
||||
|
||||
public void Add<T>(T index) where T : IZeroIndexDefinition
|
||||
{
|
||||
Items.Add(new(typeof(T), () => index));
|
||||
}
|
||||
|
||||
public void AddRange(params Type[] indexes)
|
||||
{
|
||||
Items.AddRange(indexes);
|
||||
foreach (Type type in indexes)
|
||||
{
|
||||
Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
public void Replace<T, TReplaceWith>()
|
||||
where T : IZeroIndexDefinition, new()
|
||||
where TReplaceWith : IZeroIndexDefinition, new()
|
||||
{
|
||||
Items.Remove(typeof(T));
|
||||
Items.Add(typeof(TReplaceWith));
|
||||
Replace(typeof(T), typeof(TReplaceWith));
|
||||
}
|
||||
|
||||
public void Replace(Type origin, Type replaceWith)
|
||||
{
|
||||
Items.Remove(origin);
|
||||
Items.Add(replaceWith);
|
||||
var item = Items.FirstOrDefault(x => x.Type == origin);
|
||||
if (item != null)
|
||||
{
|
||||
Items.Remove(item);
|
||||
}
|
||||
Add(replaceWith);
|
||||
}
|
||||
|
||||
public IEnumerable<IZeroIndexDefinition> GetAllForRegistration(IZeroOptions options)
|
||||
public IEnumerable<IZeroIndexDefinition> BuildAll(IZeroOptions options, IDocumentStore store)
|
||||
{
|
||||
foreach (Type type in Items)
|
||||
foreach (Map map in Items)
|
||||
{
|
||||
IZeroIndexDefinition index = (IZeroIndexDefinition)Activator.CreateInstance(type);
|
||||
index.Setup(options);
|
||||
IZeroIndexDefinition index = map.CreateIndex.Compile().Invoke();
|
||||
index.Setup(options, store);
|
||||
index.RunModifiers(options);
|
||||
yield return index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
using Raven.Client.Documents;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Models;
|
||||
using zero.Core.Renderer;
|
||||
|
||||
namespace zero.Core.Options
|
||||
{
|
||||
public class SearchOptions : ZeroBackofficeCollection<SearchIndexMap>, IZeroCollectionOptions
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
|
||||
public SearchOptions()
|
||||
{
|
||||
IsEnabled = true;
|
||||
Map<Page>();
|
||||
Map<MediaFolder>();
|
||||
}
|
||||
|
||||
|
||||
public SearchIndexMap<T> Map<T>(string group = null) where T : ZeroEntity, new()
|
||||
{
|
||||
SearchIndexMap<T> map = new(group);
|
||||
Items.Add(map);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SearchIndexMap
|
||||
{
|
||||
protected Type _type;
|
||||
protected string _group;
|
||||
protected string[] _fields;
|
||||
protected Func<ZeroEntity, SearchResult, Task> _modify;
|
||||
|
||||
const string mapTemplate = @"map('{collection}', function (x) {
|
||||
return {
|
||||
Id: x.Id,
|
||||
Group: '{group}',
|
||||
Name: x.Name,
|
||||
IsActive: x.IsActive,
|
||||
Fields: [{fields}]
|
||||
};
|
||||
});";
|
||||
|
||||
internal SearchIndexMap(Type type, string group)
|
||||
{
|
||||
_type = type;
|
||||
_group = group;
|
||||
}
|
||||
|
||||
internal string BuildInstruction(IZeroIndexDefinition index, IDocumentStore store)
|
||||
{
|
||||
return TokenReplacement.Apply(mapTemplate, new()
|
||||
{
|
||||
{ "collection", store.Conventions.GetCollectionName(_type) },
|
||||
{ "group", _group },
|
||||
{ "fields", BuildFieldArray(_fields) }
|
||||
});
|
||||
}
|
||||
|
||||
internal string BuildFieldArray(string[] fields)
|
||||
{
|
||||
if (fields == null || !fields.Any())
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
return "x." + String.Join(", x.", fields);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SearchIndexMap<T> : SearchIndexMap where T : ZeroEntity
|
||||
{
|
||||
internal SearchIndexMap(string group) : base(typeof(T), group) {}
|
||||
|
||||
public SearchIndexMap<T> Display(Action<T, SearchResult> modify = null)
|
||||
{
|
||||
_modify = (x, res) =>
|
||||
{
|
||||
modify?.Invoke(x as T, res);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public SearchIndexMap<T> Display(Func<T, SearchResult, Task> modify = null)
|
||||
{
|
||||
_modify = (x, res) => modify?.Invoke(x as T, res);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SearchIndexMap<T> Fields(params string[] fieldNames)
|
||||
{
|
||||
_fields = fieldNames;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ namespace zero.Core.Options
|
||||
Raven.Indexes.Add<Pages_ByHierarchy>();
|
||||
Raven.Indexes.Add<Pages_WithChildren>();
|
||||
Raven.Indexes.Add<Routes_ForResolver>();
|
||||
|
||||
Search = new();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -106,6 +108,9 @@ namespace zero.Core.Options
|
||||
|
||||
/// <inheritdoc />
|
||||
public InterceptorOptions Interceptors { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public SearchOptions Search { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -211,5 +216,10 @@ namespace zero.Core.Options
|
||||
///
|
||||
/// </summary>
|
||||
InterceptorOptions Interceptors { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
SearchOptions Search { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
name: 'app-search',
|
||||
|
||||
data: () => ({
|
||||
open: false,
|
||||
open: true,
|
||||
query: null,
|
||||
model: {
|
||||
page: 1,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Identity;
|
||||
using zero.Core.Models;
|
||||
using zero.Web.Services;
|
||||
|
||||
namespace zero.Web.Controllers
|
||||
@@ -16,6 +17,6 @@ namespace zero.Web.Controllers
|
||||
SearchService = searchService;
|
||||
}
|
||||
|
||||
public async Task<ListResult<ZeroEntity>> Query([FromQuery] string query) => await SearchService.Query(query);
|
||||
public async Task<ListResult<SearchResult>> Query([FromQuery] string query) => await SearchService.Query(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Queries;
|
||||
using Raven.Client.Documents.Session;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Database.Indexes;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
using zero.Core.Models;
|
||||
|
||||
namespace zero.Web.Services
|
||||
{
|
||||
@@ -19,22 +23,40 @@ namespace zero.Web.Services
|
||||
}
|
||||
|
||||
|
||||
public async Task<ListResult<ZeroEntity>> Query(string searchTerm)
|
||||
public async Task<ListResult<SearchResult>> Query(string searchTerm)
|
||||
{
|
||||
List<ZeroEntity> results = await Session.Query<Backoffice_Search.Result, Backoffice_Search>()
|
||||
string[] searchParts = searchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(x =>
|
||||
{
|
||||
return "*" + x + "*";
|
||||
}).ToArray();
|
||||
|
||||
List<ZeroEntity> results = await Session.Query<SearchIndexResult, Backoffice_Search>()
|
||||
.Statistics(out QueryStatistics stats)
|
||||
.Search(x => x.Name, searchTerm, 2)
|
||||
.Search(x => x.Fields, searchTerm, 1, SearchOptions.Or)
|
||||
.Search(x => x.Name, searchParts, 2, @operator: SearchOperator.And)
|
||||
.Search(x => x.Fields, searchParts, 1, SearchOptions.Or, @operator: SearchOperator.And)
|
||||
.Paging(1, 20)
|
||||
.ProjectInto<ZeroEntity>()
|
||||
.ToListAsync();
|
||||
|
||||
return new ListResult<ZeroEntity>(results, stats.TotalResults, 1, 20);
|
||||
List<SearchResult> items = new();
|
||||
|
||||
foreach (ZeroEntity result in results)
|
||||
{
|
||||
items.Add(new()
|
||||
{
|
||||
Id = result.Id,
|
||||
Name = result.Name,
|
||||
IsActive = result.IsActive,
|
||||
Url = "/"
|
||||
});
|
||||
}
|
||||
|
||||
return new ListResult<SearchResult>(items, stats.TotalResults, 1, 20);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IBackofficeSearchService
|
||||
{
|
||||
Task<ListResult<ZeroEntity>> Query(string searchTerm);
|
||||
Task<ListResult<SearchResult>> Query(string searchTerm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace zero.Web
|
||||
// create all indexes
|
||||
if (options.SetupCompleted)
|
||||
{
|
||||
var indexes = options.Raven.Indexes.GetAllForRegistration(options);
|
||||
var indexes = options.Raven.Indexes.BuildAll(options, store);
|
||||
IndexCreation.CreateIndexes(indexes, store, database: options.Raven.Database);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user