From 6935426784f16853a6e3061cd141f9b35efd034b Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 26 Feb 2019 21:34:19 +0100 Subject: [PATCH] Some progress. --- .../Contents/MongoContentCollection.cs | 123 ++++++++++-- .../Contents/MongoContentDraftCollection.cs | 153 -------------- .../Contents/MongoContentEntity.cs | 4 - .../MongoContentPublishedCollection.cs | 67 ------- .../Contents/MongoContentRepository.cs | 54 +++-- .../MongoContentRepository_EventHandling.cs | 8 +- .../MongoContentRepository_SnapshotStore.cs | 14 +- .../{FindExtensions.cs => FilterFactory.cs} | 44 +++-- .../Repositories/IContentRepository.cs | 2 - .../Contents/Text/GrainTextIndexer.cs | 76 +++++++ .../Contents/Text/ITextIndexer.cs | 25 +++ .../Contents/Text/ITextIndexerGrain.cs | 24 +++ .../Contents/Text/IndexData.cs | 18 ++ .../Contents/Text/SearchContext.cs | 23 +++ .../Contents/Text/TextIndexer.cs | 47 ----- .../Contents/Text/TextIndexerGrain.cs | 187 ++++++++---------- src/Squidex/Config/Domain/EntitiesServices.cs | 4 + .../Contents/Text/TextIndexerGrainTests.cs | 44 +++-- 18 files changed, 444 insertions(+), 473 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs delete mode 100644 src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs rename src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/{FindExtensions.cs => FilterFactory.cs} (82%) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexer.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexerGrain.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Text/IndexData.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Text/SearchContext.cs delete mode 100644 src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexer.cs diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index f77be31ba..f3c95069c 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs @@ -11,50 +11,57 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { internal class MongoContentCollection : MongoRepositoryBase { - private readonly string collectionName; - protected IJsonSerializer Serializer { get; } - public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName) + public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer) : base(database) { - this.collectionName = collectionName; - Serializer = serializer; } - protected override async Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct = default) + protected override Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct = default) { - await collection.Indexes.CreateOneAsync( - new CreateIndexModel(Index.Ascending(x => x.ReferencedIds)), cancellationToken: ct); + return collection.Indexes.CreateManyAsync(new[] + { + new CreateIndexModel(Index + .Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id).Ascending(x => x.Status)), + new CreateIndexModel(Index + .Ascending(x => x.ScheduledAt).Ascending(x => x.IsDeleted)), + new CreateIndexModel(Index + .Ascending(x => x.ReferencedIds)) + }, ct); } protected override string CollectionName() { - return collectionName; + return "State_Contents"; } - public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Query query, Status[] status = null, bool useDraft = false) + public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Query query, List ids, Status[] status = null, bool useDraft = false) { try { query = query.AdjustToModel(schema.SchemaDef, useDraft); - var filter = query.ToFilter(schema.Id, status); + var filter = query.ToFilter(schema.Id, ids, status); var contentCount = Collection.Find(filter).CountDocumentsAsync(); var contentItems = @@ -62,7 +69,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents .ContentTake(query) .ContentSkip(query) .ContentSort(query) - .Not(x => x.DataText) .ToListAsync(); await Task.WhenAll(contentItems, contentCount); @@ -89,12 +95,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet ids, Status[] status = null) { - var find = - status != null && status.Length > 0 ? - Collection.Find(x => x.IndexedSchemaId == schema.Id && ids.Contains(x.Id) && x.IsDeleted != true && status.Contains(x.Status)) : - Collection.Find(x => x.IndexedSchemaId == schema.Id && ids.Contains(x.Id)); + var find = Collection.Find(FilterFactory.Build(schema.Id, ids, status)); - var contentItems = find.Not(x => x.DataText).ToListAsync(); + var contentItems = find.ToListAsync(); var contentCount = find.CountDocumentsAsync(); await Task.WhenAll(contentItems, contentCount); @@ -107,6 +110,66 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return ResultList.Create(contentCount.Result, contentItems.Result); } + public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, Status[] status = null) + { + var find = Collection.Find(FilterFactory.Build(schema.Id, id, status)); + + var contentEntity = await find.FirstOrDefaultAsync(); + + contentEntity?.ParseData(schema.SchemaDef, Serializer); + + return contentEntity; + } + + public Task QueryScheduledWithoutDataAsync(Instant now, Func callback) + { + return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true) + .Not(x => x.DataByIds) + .Not(x => x.DataDraftByIds) + .ForEachAsync(c => + { + callback(c); + }); + } + + public async Task> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode) + { + var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id); + + var contentEntities = + await Collection.Find(filter).Only(x => x.Id) + .ToListAsync(); + + return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); + } + + public async Task> QueryIdsAsync(Guid appId) + { + var contentEntities = + await Collection.Find(x => x.IndexedAppId == appId).Only(x => x.Id) + .ToListAsync(); + + return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); + } + + public async Task<(ContentState Value, long Version)> ReadAsync(Guid key, Func> getSchema) + { + var contentEntity = + await Collection.Find(x => x.Id == key) + .FirstOrDefaultAsync(); + + if (contentEntity != null) + { + var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); + + contentEntity.ParseData(schema.SchemaDef, Serializer); + + return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); + } + + return (null, EtagVersion.NotFound); + } + public Task CleanupAsync(Guid id) { return Collection.UpdateManyAsync( @@ -120,5 +183,31 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { return Collection.DeleteOneAsync(x => x.Id == id); } + + public async Task UpsertAsync(MongoContentEntity content, long oldVersion) + { + try + { + await Collection.ReplaceOneAsync(x => x.Id == content.Id && x.Version == oldVersion, content, Upsert); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + var existingVersion = + await Collection.Find(x => x.Id == content.Id).Only(x => x.Id, x => x.Version) + .FirstOrDefaultAsync(); + + if (existingVersion != null) + { + throw new InconsistentStateException(existingVersion["vs"].AsInt64, oldVersion, ex); + } + } + else + { + throw; + } + } + } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs deleted file mode 100644 index 653e35172..000000000 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs +++ /dev/null @@ -1,153 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using MongoDB.Driver; -using NodaTime; -using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Domain.Apps.Entities.Contents.State; -using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.Queries; -using Squidex.Infrastructure.Reflection; -using Squidex.Infrastructure.States; - -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents -{ - internal sealed class MongoContentDraftCollection : MongoContentCollection - { - private readonly MongoDbOptions options; - - public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer, IOptions options) - : base(database, serializer, "State_Content_Draft") - { - this.options = options.Value; - } - - protected override async Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct = default) - { - await collection.Indexes.CreateOneAsync( - new CreateIndexModel( - Index - .Ascending(x => x.IndexedSchemaId) - .Ascending(x => x.Id) - .Ascending(x => x.IsDeleted)), null, ct); - - if (!options.IsCosmosDb) - { - await collection.Indexes.CreateOneAsync( - new CreateIndexModel( - Index - .Text(x => x.DataText) - .Ascending(x => x.IndexedSchemaId) - .Ascending(x => x.IsDeleted) - .Ascending(x => x.Status)), null, ct); - } - - await base.SetupCollectionAsync(collection, ct); - } - - public async Task> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode) - { - var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id); - - var contentEntities = - await Collection.Find(filter).Only(x => x.Id) - .ToListAsync(); - - return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); - } - - public async Task> QueryIdsAsync(Guid appId) - { - var contentEntities = - await Collection.Find(x => x.IndexedAppId == appId).Only(x => x.Id) - .ToListAsync(); - - return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); - } - - public Task QueryScheduledWithoutDataAsync(Instant now, Func callback) - { - return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true) - .Not(x => x.DataByIds) - .Not(x => x.DataDraftByIds) - .Not(x => x.DataText) - .ForEachAsync(c => - { - callback(c); - }); - } - - public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) - { - var contentEntity = - await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText) - .FirstOrDefaultAsync(); - - contentEntity?.ParseData(schema.SchemaDef, Serializer); - - return contentEntity; - } - - public async Task<(ContentState Value, long Version)> ReadAsync(Guid key, Func> getSchema) - { - var contentEntity = - await Collection.Find(x => x.Id == key).Not(x => x.DataText) - .FirstOrDefaultAsync(); - - if (contentEntity != null) - { - var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); - - contentEntity.ParseData(schema.SchemaDef, Serializer); - - return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); - } - - return (null, EtagVersion.NotFound); - } - - public async Task UpsertAsync(MongoContentEntity content, long oldVersion) - { - try - { - content.DataText = content.DataDraftByIds.ToFullText(); - - await Collection.ReplaceOneAsync(x => x.Id == content.Id && x.Version == oldVersion, content, Upsert); - } - catch (MongoWriteException ex) - { - if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) - { - var existingVersion = - await Collection.Find(x => x.Id == content.Id).Only(x => x.Id, x => x.Version) - .FirstOrDefaultAsync(); - - if (existingVersion != null) - { - throw new InconsistentStateException(existingVersion["vs"].AsInt64, oldVersion, ex); - } - } - else - { - throw; - } - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index a2346db31..9c8f3eba7 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -69,10 +69,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents [BsonJson] public ScheduleJob ScheduleJob { get; set; } - [BsonIgnoreIfDefault] - [BsonElement("dt")] - public string DataText { get; set; } - [BsonRequired] [BsonElement("ai")] public NamedId AppId { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs deleted file mode 100644 index 08d819402..000000000 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using MongoDB.Driver; -using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents -{ - internal sealed class MongoContentPublishedCollection : MongoContentCollection - { - private readonly MongoDbOptions options; - - public MongoContentPublishedCollection(IMongoDatabase database, IJsonSerializer serializer, IOptions options) - : base(database, serializer, "State_Content_Published") - { - this.options = options.Value; - } - - protected override async Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct = default) - { - await collection.Indexes.CreateOneAsync( - new CreateIndexModel(Index.Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id)), null, ct); - - if (!options.IsCosmosDb) - { - await collection.Indexes.CreateOneAsync( - new CreateIndexModel(Index.Text(x => x.DataText).Ascending(x => x.IndexedSchemaId)), null, ct); - } - - await base.SetupCollectionAsync(collection, ct); - } - - public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) - { - var contentEntity = - await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText) - .FirstOrDefaultAsync(); - - contentEntity?.ParseData(schema.SchemaDef, Serializer); - - return contentEntity; - } - - public Task UpsertAsync(MongoContentEntity content) - { - content.DataText = content.DataByIds.ToFullText(); - content.DataDraftByIds = null; - content.ScheduleJob = null; - content.ScheduledAt = null; - - return Collection.ReplaceOneAsync(x => x.Id == content.Id, content, new UpdateOptions { IsUpsert = true }); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index f24c6be1d..ea8fd726b 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -9,18 +9,17 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Options; using MongoDB.Driver; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents @@ -30,28 +29,26 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents private readonly IMongoDatabase database; private readonly IAppProvider appProvider; private readonly IJsonSerializer serializer; - private readonly MongoContentDraftCollection contentsDraft; - private readonly MongoContentPublishedCollection contentsPublished; + private readonly ITextIndexer indexer; + private readonly MongoContentCollection contents; - public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, IOptions options) + public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, ITextIndexer indexer) { Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(serializer, nameof(serializer)); - Guard.NotNull(options, nameof(options)); + Guard.NotNull(indexer, nameof(ITextIndexer)); this.appProvider = appProvider; - + this.database = database; + this.indexer = indexer; this.serializer = serializer; - contentsDraft = new MongoContentDraftCollection(database, serializer, options); - contentsPublished = new MongoContentPublishedCollection(database, serializer, options); - - this.database = database; + contents = new MongoContentCollection(database, serializer); } public Task InitializeAsync(CancellationToken ct = default) { - return Task.WhenAll(contentsDraft.InitializeAsync(ct), contentsPublished.InitializeAsync(ct)); + return contents.InitializeAsync(ct); } public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query) @@ -60,11 +57,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { if (RequiresPublished(status)) { - return await contentsPublished.QueryAsync(app, schema, query); + var ids = await indexer.SearchAsync(query.FullText, app, schema); + + return await contents.QueryAsync(app, schema, query, ids); } else { - return await contentsDraft.QueryAsync(app, schema, query, status, true); + var ids = await indexer.SearchAsync(query.FullText, app, schema, true); + + return await contents.QueryAsync(app, schema, query, ids, status, true); } } } @@ -75,11 +76,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { if (RequiresPublished(status)) { - return await contentsPublished.QueryAsync(app, schema, ids); + return await contents.QueryAsync(app, schema, ids); } else { - return await contentsDraft.QueryAsync(app, schema, ids, status); + return await contents.QueryAsync(app, schema, ids, status); } } } @@ -90,11 +91,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { if (RequiresPublished(status)) { - return await contentsPublished.FindContentAsync(app, schema, id); + return await contents.FindContentAsync(app, schema, id); } else { - return await contentsDraft.FindContentAsync(app, schema, id); + return await contents.FindContentAsync(app, schema, id, status); } } } @@ -103,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { using (Profiler.TraceMethod()) { - return await contentsDraft.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode); + return await contents.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode); } } @@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { using (Profiler.TraceMethod()) { - return await contentsDraft.QueryIdsAsync(appId); + return await contents.QueryIdsAsync(appId); } } @@ -119,22 +120,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { using (Profiler.TraceMethod()) { - await contentsDraft.QueryScheduledWithoutDataAsync(now, callback); + await contents.QueryScheduledWithoutDataAsync(now, callback); } } - public Task RemoveAsync(Guid appId) - { - return Task.WhenAll( - contentsDraft.RemoveAsync(appId), - contentsPublished.RemoveAsync(appId)); - } - public Task ClearAsync() { - return Task.WhenAll( - contentsDraft.ClearAsync(), - contentsPublished.ClearAsync()); + return contents.ClearAsync(); } public Task DeleteArchiveAsync() diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs index 427629d1d..bb072df70 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -33,16 +33,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents protected Task On(AssetDeleted @event) { - return Task.WhenAll( - contentsDraft.CleanupAsync(@event.AssetId), - contentsPublished.CleanupAsync(@event.AssetId)); + return contents.CleanupAsync(@event.AssetId); } protected Task On(ContentDeleted @event) { - return Task.WhenAll( - contentsDraft.CleanupAsync(@event.ContentId), - contentsPublished.CleanupAsync(@event.ContentId)); + return contents.CleanupAsync(@event.ContentId); } Task IEventConsumer.ClearAsync() diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index 32f38e982..a1a9620dd 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs @@ -7,7 +7,6 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -23,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { using (Profiler.TraceMethod()) { - return await contentsDraft.ReadAsync(key, GetSchemaAsync); + return await contents.ReadAsync(key, GetSchemaAsync); } } @@ -58,16 +57,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents Version = newVersion }); - await contentsDraft.UpsertAsync(content, oldVersion); + await contents.UpsertAsync(content, oldVersion); - if (value.Status == Status.Published && !value.IsDeleted) - { - await contentsPublished.UpsertAsync(content); - } - else - { - await contentsPublished.RemoveAsync(content.Id); - } + await indexer.IndexAsync(value.SchemaId.Id, value.Id, value.Data, value.DataDraft); } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs similarity index 82% rename from src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs index 14195985f..152e98b87 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs @@ -19,7 +19,7 @@ using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { - public static class FindExtensions + public static class FilterFactory { private static readonly FilterDefinitionBuilder Filter = Builders.Filter; @@ -109,7 +109,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors return cursor.Skip(query); } - public static FilterDefinition ToFilter(this Query query, Guid schemaId, Status[] status) + public static FilterDefinition Build(Guid schemaId, Guid id, Status[] status) + { + return CreateFilter(schemaId, new List { id }, status, null); + } + + public static FilterDefinition Build(Guid schemaId, ICollection ids, Status[] status) + { + return CreateFilter(schemaId, ids, status, null); + } + + public static FilterDefinition ToFilter(this Query query, Guid schemaId, ICollection ids, Status[] status) + { + return CreateFilter(schemaId, ids, status, query); + } + + private static FilterDefinition CreateFilter(Guid schemaId, ICollection ids, Status[] status, Query query) { var filters = new List> { @@ -117,25 +132,28 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors Filter.Ne(x => x.IsDeleted, true) }; - if (status != null) - { - filters.Add(Filter.In(x => x.Status, status)); - } - - var filter = query.BuildFilter(); - - if (filter.Filter != null) + if (ids != null && ids.Count > 0) { - if (filter.Last) + if (ids.Count > 1) { - filters.Add(filter.Filter); + filters.Add(Filter.In(x => x.Id, ids)); } else { - filters.Insert(0, filter.Filter); + filters.Add(Filter.Eq(x => x.Id, ids.First())); } } + if (status != null) + { + filters.Add(Filter.In(x => x.Status, status)); + } + + if (query.Filter != null) + { + filters.Add(query.Filter.BuildFilter()); + } + return Filter.And(filters); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index e82d39932..7944c1b32 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -28,7 +28,5 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id); Task QueryScheduledWithoutDataAsync(Instant now, Func callback); - - Task RemoveAsync(Guid appId); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs new file mode 100644 index 000000000..6cf376033 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs @@ -0,0 +1,76 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Orleans; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public sealed class GrainTextIndexer : ITextIndexer + { + private readonly IGrainFactory grainFactory; + + public GrainTextIndexer(IGrainFactory grainFactory) + { + Guard.NotNull(grainFactory, nameof(grainFactory)); + + this.grainFactory = grainFactory; + } + + public Task DeleteAsync(Guid schemaId, Guid id) + { + var index = grainFactory.GetGrain(schemaId); + + return index.DeleteAsync(id); + } + + public async Task IndexAsync(Guid schemaId, Guid id, NamedContentData data, NamedContentData dataDraft) + { + var index = grainFactory.GetGrain(schemaId); + + if (data != null) + { + await index.IndexAsync(id, new IndexData { }); + } + + if (dataDraft != null) + { + await index.IndexAsync(id, new IndexData { IsDraft = true }); + } + } + + public async Task> SearchAsync(string queryText, IAppEntity app, ISchemaEntity schema, bool useDraft = false) + { + if (string.IsNullOrWhiteSpace(queryText)) + { + return null; + } + + var index = grainFactory.GetGrain(schema.Id); + + var languages = app.LanguagesConfig.Select(x => x.Key).ToList(); + + var context = new SearchContext + { + AppVersion = app.Version, + Schema = schema.SchemaDef, + SchemaVersion = schema.Version, + Languages = languages, + IsDraft = useDraft + }; + + return await index.SearchAsync(queryText, context); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexer.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexer.cs new file mode 100644 index 000000000..27d552d20 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexer.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Schemas; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public interface ITextIndexer + { + Task DeleteAsync(Guid schemaId, Guid id); + + Task IndexAsync(Guid schemaId, Guid id, NamedContentData data, NamedContentData dataDraft); + + Task> SearchAsync(string queryText, IAppEntity appEntity, ISchemaEntity schemaEntity, bool useDraft = false); + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexerGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexerGrain.cs new file mode 100644 index 000000000..8bd3884d1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexerGrain.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Orleans; +using Squidex.Infrastructure.Orleans; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public interface ITextIndexerGrain : IGrainWithGuidKey + { + Task DeleteAsync(Guid id); + + Task IndexAsync(Guid id, J data); + + Task> SearchAsync(string queryText, J context); + } +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexData.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexData.cs new file mode 100644 index 000000000..7911be4a7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/IndexData.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public sealed class IndexData + { + public NamedContentData Data { get; set; } + + public bool IsDraft { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/SearchContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/SearchContext.cs new file mode 100644 index 000000000..628613c53 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/SearchContext.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Entities.Contents.Text +{ + public sealed class SearchContext + { + public bool IsDraft { get; set; } + + public long AppVersion { get; set; } + + public long SchemaVersion { get; set; } + + public List Languages { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexer.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexer.cs deleted file mode 100644 index db309826f..000000000 --- a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Domain.Apps.Entities.Contents.Text -{ - public sealed class TextIndexer : IEventConsumer - { - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^content-"; } - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - - public Task On(Envelope @event) - { - switch (@event.Payload) - { - case ContentCreated contentCreated: - break; - case ContentUpdated contentUpdated: - break; - case ContentUpdateProposed contentUpdateProposed: - break; - } - - return Task.CompletedTask; - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs index 119df813f..852deca7e 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs @@ -8,18 +8,16 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text; using System.Threading.Tasks; using Lucene.Net.Analysis; -using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.QueryParsers.Classic; using Lucene.Net.Search; using Lucene.Net.Store; using Lucene.Net.Util; -using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Json.Objects; @@ -27,7 +25,7 @@ using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Entities.Contents.Text { - public sealed class TextIndexerGrain : GrainOfGuid + public sealed class TextIndexerGrain : GrainOfGuid, ITextIndexerGrain { private const LuceneVersion Version = LuceneVersion.LUCENE_48; private const int MaxResults = 2000; @@ -39,9 +37,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private IndexWriter indexWriter; private IndexReader indexReader; private QueryParser queryParser; - private int currentAppVersion; - private int currentSchemaVersion; - private int updates; + private long currentAppVersion; + private long currentSchemaVersion; + private long updates; public TextIndexerGrain(IAssetStore assetStore) { @@ -72,147 +70,133 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text indexReader = indexWriter.GetReader(true); } - public Task DeleteContentAsync(Guid id) + public Task DeleteAsync(Guid id) { indexWriter.DeleteDocuments(new Term("id", id.ToString())); return TryFlushAsync(); } - public Task AddContentAsync(Guid id, NamedContentData data, bool isUpdate, bool isDraft) + public Task IndexAsync(Guid id, J data) { - var idString = id.ToString(); + string idString = id.ToString(), draft = data.Value.IsDraft.ToString(); - if (isUpdate) - { - indexWriter.DeleteDocuments(new Term("id", idString)); - } + indexWriter.DeleteDocuments( + new Term("id", idString), + new Term("dd", draft)); var document = new Document(); document.AddStringField("id", idString, Field.Store.YES); - document.AddInt32Field("draft", isDraft ? 1 : 0, Field.Store.YES); + document.AddStringField("dd", draft, Field.Store.YES); - foreach (var field in data) + var languages = new Dictionary(); + + void AppendText(string language, string text) { - foreach (var fieldValue in field.Value) + if (!string.IsNullOrWhiteSpace(text)) { - var value = fieldValue.Value; + var sb = languages.GetOrAddNew(language); - if (value.Type == JsonValueType.String) + if (sb.Length > 0) { - var fieldName = BuildFieldName(fieldValue.Key, field.Key); - - document.AddTextField(fieldName, fieldValue.Value.ToString(), Field.Store.YES); + sb.Append(" "); } - else if (value.Type == JsonValueType.Object) - { - foreach (var property in (JsonObject)value) - { - if (property.Value.Type == JsonValueType.String) - { - var fieldName = BuildFieldName(fieldValue.Key, field.Key, property.Key); - document.AddTextField(fieldName, property.Value.ToString(), Field.Store.YES); - } - } - } + sb.Append(text); + } + } + + foreach (var field in data.Value.Data) + { + foreach (var fieldValue in field.Value) + { + var appendText = new Action(text => AppendText(fieldValue.Key, text)); + + AppendJsonText(fieldValue.Value, appendText); } } + foreach (var field in languages) + { + document.AddTextField(field.Key, field.Value.ToString(), Field.Store.NO); + } + indexWriter.AddDocument(document); return TryFlushAsync(); } - public Task> SearchAsync(string term, int appVersion, int schemaVersion, J schema, List languages) + private static void AppendJsonText(IJsonValue value, Action appendText) { - var query = BuildQuery(term, appVersion, schemaVersion, schema, languages); + if (value.Type == JsonValueType.String) + { + var text = value.ToString(); - var result = new List(); + appendText(text); + } + else if (value is JsonArray array) + { + foreach (var item in array) + { + AppendJsonText(item, appendText); + } + } + else if (value is JsonObject obj) + { + foreach (var item in obj.Values) + { + AppendJsonText(item, appendText); + } + } + } - if (indexReader != null) + public Task> SearchAsync(string queryText, J context) + { + var result = new HashSet(); + + if (!string.IsNullOrWhiteSpace(queryText)) { - var hits = new IndexSearcher(indexReader).Search(query, MaxResults).ScoreDocs; + var query = BuildQuery(queryText, context); - foreach (var hit in hits) + if (indexReader != null) { - var document = indexReader.Document(hit.Doc); + var filter = new QueryWrapperFilter(new TermQuery(new Term("dd", context.Value.IsDraft.ToString()))); - var idField = document.GetField("id")?.GetStringValue(); + var hits = new IndexSearcher(indexReader).Search(query, MaxResults).ScoreDocs; - if (idField != null && Guid.TryParse(idField, out var guid)) + foreach (var hit in hits) { - result.Add(guid); + var document = indexReader.Document(hit.Doc); + + var idField = document.GetField("id")?.GetStringValue(); + + if (idField != null && Guid.TryParse(idField, out var guid)) + { + result.Add(guid); + } } } } - return Task.FromResult(result); + return Task.FromResult(result.ToList()); } - private Query BuildQuery(string query, int appVersion, int schemaVersion, J schema, List language) + private Query BuildQuery(string query, SearchContext context) { - if (queryParser == null || currentAppVersion != appVersion || currentSchemaVersion != schemaVersion) + if (queryParser == null || currentAppVersion != context.AppVersion || currentSchemaVersion != context.SchemaVersion) { - var fields = BuildFields(schema, language); + var fields = context.Languages.Select(BuildFieldName).ToArray(); queryParser = new MultiFieldQueryParser(Version, fields, Analyzer); - currentAppVersion = appVersion; - currentSchemaVersion = schemaVersion; + currentAppVersion = context.AppVersion; + currentSchemaVersion = context.SchemaVersion; } return queryParser.Parse(query); } - private string[] BuildFields(Schema schema, IEnumerable languages) - { - var fieldNames = new List(); - - var iv = InvariantPartitioning.Instance.Master.Key; - - foreach (var field in schema.Fields) - { - if (field.RawProperties is StringFieldProperties) - { - if (field.Partitioning.Equals(Partitioning.Invariant)) - { - fieldNames.Add(BuildFieldName(iv, field.Name)); - } - else - { - foreach (var language in languages) - { - fieldNames.Add(BuildFieldName(language, field.Name)); - } - } - } - else if (field is IArrayField arrayField) - { - foreach (var nested in arrayField.Fields) - { - if (nested.RawProperties is StringFieldProperties) - { - if (field.Partitioning.Equals(Partitioning.Invariant)) - { - fieldNames.Add(BuildFieldName(iv, field.Name, nested.Name)); - } - else - { - foreach (var language in languages) - { - fieldNames.Add(BuildFieldName(language, field.Name, nested.Name)); - } - } - } - } - } - } - - return fieldNames.ToArray(); - } - private async Task TryFlushAsync() { updates++; @@ -255,14 +239,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text } } - private static string BuildFieldName(string language, string name) - { - return $"{language}_{name}"; - } - - private static string BuildFieldName(string language, string name, string nested) + private static string BuildFieldName(string language) { - return $"{language}_{name}_{nested}"; + return $"field_{language}"; } } } diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index c7a215257..26d62ef32 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -31,6 +31,7 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.GraphQL; +using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; @@ -105,6 +106,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As>(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests.cs index ba4c93d1c..4d005609d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests.cs @@ -22,15 +22,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text new Schema("test") .AddString(1, "test", Partitioning.Invariant) .AddString(2, "localized", Partitioning.Language); - private readonly List languages = new List { "de", "en" }; private readonly Guid schemaId = Guid.NewGuid(); private readonly List ids1 = new List { Guid.NewGuid() }; private readonly List ids2 = new List { Guid.NewGuid() }; + private readonly SearchContext context; private readonly IAssetStore assetStore = new MemoryAssetStore(); private readonly TextIndexerGrain sut; public TextIndexerGrainTests() { + context = new SearchContext + { + AppVersion = 1, + Schema = schema, + SchemaVersion = 1, + Languages = new List { "de", "en" } + }; + sut = new TextIndexerGrain(assetStore); sut.ActivateAsync(schemaId).Wait(); } @@ -52,11 +60,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { await other.ActivateAsync(schemaId); - var helloIds = await other.SearchAsync("Hello", 0, 0, schema, languages); + var helloIds = await other.SearchAsync("Hello", context); Assert.Equal(ids1, helloIds); - var worldIds = await other.SearchAsync("World", 0, 0, schema, languages); + var worldIds = await other.SearchAsync("World", context); Assert.Equal(ids2, worldIds); } @@ -71,11 +79,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { await AddInvariantContent(); - var helloIds = await sut.SearchAsync("Hello", 0, 0, schema, languages); + var helloIds = await sut.SearchAsync("Hello", context); Assert.Equal(ids1, helloIds); - var worldIds = await sut.SearchAsync("World", 0, 0, schema, languages); + var worldIds = await sut.SearchAsync("World", context); Assert.Equal(ids2, worldIds); } @@ -85,14 +93,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { await AddInvariantContent(); - await sut.DeleteContentAsync(ids1[0]); + await sut.DeleteAsync(ids1[0]); await sut.FlushAsync(); - var helloIds = await sut.SearchAsync("Hello", 0, 0, schema, languages); + var helloIds = await sut.SearchAsync("Hello", context); Assert.Empty(helloIds); - var worldIds = await sut.SearchAsync("World", 0, 0, schema, languages); + var worldIds = await sut.SearchAsync("World", context); Assert.Equal(ids2, worldIds); } @@ -102,20 +110,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text { await AddLocalizedContent(); - var german1 = await sut.SearchAsync("Stadt", 0, 0, schema, languages); - var german2 = await sut.SearchAsync("and", 0, 0, schema, languages); + var german1 = await sut.SearchAsync("Stadt", context); + var german2 = await sut.SearchAsync("and", context); - var germanStopwordsIds = await sut.SearchAsync("und", 0, 0, schema, languages); + var germanStopwordsIds = await sut.SearchAsync("und", context); Assert.Equal(ids1, german1); Assert.Equal(ids1, german2); Assert.Equal(ids2, germanStopwordsIds); - var english1 = await sut.SearchAsync("City", 0, 0, schema, languages); - var english2 = await sut.SearchAsync("und", 0, 0, schema, languages); + var english1 = await sut.SearchAsync("City", context); + var english2 = await sut.SearchAsync("und", context); - var englishStopwordsIds = await sut.SearchAsync("and", 0, 0, schema, languages); + var englishStopwordsIds = await sut.SearchAsync("and", context); Assert.Equal(ids2, english1); Assert.Equal(ids2, english2); @@ -137,8 +145,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text new ContentFieldData() .AddValue("en", "City and Surroundings und sonstiges")); - await sut.AddContentAsync(ids1[0], germanData, false, false); - await sut.AddContentAsync(ids2[0], englishData, false, false); + await sut.IndexAsync(ids1[0], new IndexData { Data = germanData }); + await sut.IndexAsync(ids2[0], new IndexData { Data = englishData }); await sut.FlushAsync(); } @@ -156,8 +164,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text new ContentFieldData() .AddValue("iv", "World")); - await sut.AddContentAsync(ids1[0], data1, false, false); - await sut.AddContentAsync(ids2[0], data2, false, false); + await sut.IndexAsync(ids1[0], new IndexData { Data = data1 }); + await sut.IndexAsync(ids2[0], new IndexData { Data = data2 }); await sut.FlushAsync(); }