mirror of https://github.com/Squidex/squidex.git
40 changed files with 804 additions and 334 deletions
@ -0,0 +1,131 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 Microsoft.OData.UriParser; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
||||
|
{ |
||||
|
internal class MongoContentCollection : MongoRepositoryBase<MongoContentEntity> |
||||
|
{ |
||||
|
private readonly string collectionName; |
||||
|
|
||||
|
public MongoContentCollection(IMongoDatabase database, string collectionName) |
||||
|
: base(database) |
||||
|
{ |
||||
|
this.collectionName = collectionName; |
||||
|
} |
||||
|
|
||||
|
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection) |
||||
|
{ |
||||
|
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds)); |
||||
|
} |
||||
|
|
||||
|
protected override string CollectionName() |
||||
|
{ |
||||
|
return collectionName; |
||||
|
} |
||||
|
|
||||
|
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, ODataUriParser odataQuery, Status[] status = null, bool useDraft = false) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var propertyCalculator = FindExtensions.CreatePropertyCalculator(schema.SchemaDef, useDraft); |
||||
|
|
||||
|
var filter = FindExtensions.BuildQuery(odataQuery, schema.Id, status, propertyCalculator); |
||||
|
|
||||
|
var contentCount = Collection.Find(filter).CountAsync(); |
||||
|
var contentItems = |
||||
|
Collection.Find(filter) |
||||
|
.ContentTake(odataQuery) |
||||
|
.ContentSkip(odataQuery) |
||||
|
.ContentSort(odataQuery, propertyCalculator) |
||||
|
.Not(x => x.DataText) |
||||
|
.ToListAsync(); |
||||
|
|
||||
|
await Task.WhenAll(contentItems, contentCount); |
||||
|
|
||||
|
foreach (var entity in contentItems.Result) |
||||
|
{ |
||||
|
entity.ParseData(schema.SchemaDef); |
||||
|
} |
||||
|
|
||||
|
return ResultList.Create<IContentEntity>(contentItems.Result, contentCount.Result); |
||||
|
} |
||||
|
catch (NotSupportedException) |
||||
|
{ |
||||
|
throw new ValidationException("This odata operation is not supported."); |
||||
|
} |
||||
|
catch (NotImplementedException) |
||||
|
{ |
||||
|
throw new ValidationException("This odata operation is not supported."); |
||||
|
} |
||||
|
catch (MongoQueryException ex) |
||||
|
{ |
||||
|
if (ex.Message.Contains("17406")) |
||||
|
{ |
||||
|
throw new DomainException("Result set is too large to be retrieved. Use $top parameter to reduce the number of items."); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> 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 contentItems = find.Not(x => x.DataText).ToListAsync(); |
||||
|
var contentCount = find.CountAsync(); |
||||
|
|
||||
|
await Task.WhenAll(contentItems, contentCount); |
||||
|
|
||||
|
foreach (var entity in contentItems.Result) |
||||
|
{ |
||||
|
entity.ParseData(schema.SchemaDef); |
||||
|
} |
||||
|
|
||||
|
return ResultList.Create<IContentEntity>(contentItems.Result, contentCount.Result); |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
|
||||
|
return contentEntity; |
||||
|
} |
||||
|
|
||||
|
public Task CleanupAsync(Guid id) |
||||
|
{ |
||||
|
return Collection.UpdateManyAsync( |
||||
|
Filter.And( |
||||
|
Filter.AnyEq(x => x.ReferencedIds, id), |
||||
|
Filter.AnyNe(x => x.ReferencedIdsDeleted, id)), |
||||
|
Update.AddToSet(x => x.ReferencedIdsDeleted, id)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 MongoDB.Driver; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Entities.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.State; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
||||
|
{ |
||||
|
internal sealed class MongoContentDraftCollection : MongoContentCollection |
||||
|
{ |
||||
|
public MongoContentDraftCollection(IMongoDatabase database) |
||||
|
: base(database, "State_Content_Draft") |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection) |
||||
|
{ |
||||
|
await collection.Indexes.CreateOneAsync( |
||||
|
Index |
||||
|
.Ascending(x => x.IndexedSchemaId) |
||||
|
.Ascending(x => x.Id) |
||||
|
.Ascending(x => x.IsDeleted)); |
||||
|
|
||||
|
await collection.Indexes.CreateOneAsync( |
||||
|
Index |
||||
|
.Text(x => x.DataText) |
||||
|
.Ascending(x => x.IndexedSchemaId) |
||||
|
.Ascending(x => x.IsDeleted) |
||||
|
.Ascending(x => x.Status)); |
||||
|
|
||||
|
await base.SetupCollectionAsync(collection); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids) |
||||
|
{ |
||||
|
var contentEntities = |
||||
|
await Collection.Find(x => x.IndexedSchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted != true).Only(x => x.Id) |
||||
|
.ToListAsync(); |
||||
|
|
||||
|
return ids.Except(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<(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); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
||||
|
{ |
||||
|
internal sealed class MongoContentPublishedCollection : MongoContentCollection |
||||
|
{ |
||||
|
public MongoContentPublishedCollection(IMongoDatabase database) |
||||
|
: base(database, "State_Content_Published") |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection) |
||||
|
{ |
||||
|
await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText).Ascending(x => x.IndexedSchemaId)); |
||||
|
|
||||
|
await collection.Indexes.CreateOneAsync( |
||||
|
Index |
||||
|
.Ascending(x => x.IndexedSchemaId) |
||||
|
.Ascending(x => x.Id)); |
||||
|
|
||||
|
await base.SetupCollectionAsync(collection); |
||||
|
} |
||||
|
|
||||
|
public Task UpsertAsync(MongoContentEntity content) |
||||
|
{ |
||||
|
content.DataText = content.DataByIds.ToFullText(); |
||||
|
content.DataDraftByIds = null; |
||||
|
|
||||
|
return Collection.ReplaceOneAsync(x => x.Id == content.Id, content, new UpdateOptions { IsUpsert = true }); |
||||
|
} |
||||
|
|
||||
|
public Task RemoveAsync(Guid id) |
||||
|
{ |
||||
|
return Collection.DeleteOneAsync(x => x.Id == id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Contents |
||||
|
{ |
||||
|
[EventType(nameof(ContentChangesConfirmed))] |
||||
|
public sealed class ContentChangesConfirmed : ContentEvent |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue