mirror of https://github.com/Squidex/squidex.git
18 changed files with 444 additions and 473 deletions
@ -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<MongoDbOptions> options) |
|
||||
: base(database, serializer, "State_Content_Draft") |
|
||||
{ |
|
||||
this.options = options.Value; |
|
||||
} |
|
||||
|
|
||||
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default) |
|
||||
{ |
|
||||
await collection.Indexes.CreateOneAsync( |
|
||||
new CreateIndexModel<MongoContentEntity>( |
|
||||
Index |
|
||||
.Ascending(x => x.IndexedSchemaId) |
|
||||
.Ascending(x => x.Id) |
|
||||
.Ascending(x => x.IsDeleted)), null, ct); |
|
||||
|
|
||||
if (!options.IsCosmosDb) |
|
||||
{ |
|
||||
await collection.Indexes.CreateOneAsync( |
|
||||
new CreateIndexModel<MongoContentEntity>( |
|
||||
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<IReadOnlyList<Guid>> 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<IReadOnlyList<Guid>> 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<IContentEntity, Task> 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<IContentEntity> 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<Guid, Guid, Task<ISchemaEntity>> 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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<MongoDbOptions> options) |
|
||||
: base(database, serializer, "State_Content_Published") |
|
||||
{ |
|
||||
this.options = options.Value; |
|
||||
} |
|
||||
|
|
||||
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default) |
|
||||
{ |
|
||||
await collection.Indexes.CreateOneAsync( |
|
||||
new CreateIndexModel<MongoContentEntity>(Index.Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id)), null, ct); |
|
||||
|
|
||||
if (!options.IsCosmosDb) |
|
||||
{ |
|
||||
await collection.Indexes.CreateOneAsync( |
|
||||
new CreateIndexModel<MongoContentEntity>(Index.Text(x => x.DataText).Ascending(x => x.IndexedSchemaId)), null, ct); |
|
||||
} |
|
||||
|
|
||||
await base.SetupCollectionAsync(collection, ct); |
|
||||
} |
|
||||
|
|
||||
public async Task<IContentEntity> 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 }); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<ITextIndexerGrain>(schemaId); |
||||
|
|
||||
|
return index.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
public async Task IndexAsync(Guid schemaId, Guid id, NamedContentData data, NamedContentData dataDraft) |
||||
|
{ |
||||
|
var index = grainFactory.GetGrain<ITextIndexerGrain>(schemaId); |
||||
|
|
||||
|
if (data != null) |
||||
|
{ |
||||
|
await index.IndexAsync(id, new IndexData { }); |
||||
|
} |
||||
|
|
||||
|
if (dataDraft != null) |
||||
|
{ |
||||
|
await index.IndexAsync(id, new IndexData { IsDraft = true }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<List<Guid>> SearchAsync(string queryText, IAppEntity app, ISchemaEntity schema, bool useDraft = false) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(queryText)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var index = grainFactory.GetGrain<ITextIndexerGrain>(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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<List<Guid>> SearchAsync(string queryText, IAppEntity appEntity, ISchemaEntity schemaEntity, bool useDraft = false); |
||||
|
} |
||||
|
} |
||||
@ -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<IndexData> data); |
||||
|
|
||||
|
Task<List<Guid>> SearchAsync(string queryText, J<SearchContext> context); |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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<string> Languages { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -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<IEvent> @event) |
|
||||
{ |
|
||||
switch (@event.Payload) |
|
||||
{ |
|
||||
case ContentCreated contentCreated: |
|
||||
break; |
|
||||
case ContentUpdated contentUpdated: |
|
||||
break; |
|
||||
case ContentUpdateProposed contentUpdateProposed: |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue