Browse Source

Custom files for snapshot store.

pull/214/head
Sebastian Stehle 8 years ago
parent
commit
8be1172231
  1. 1
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs
  2. 59
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  3. 67
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository_SnapshotStore.cs
  4. 64
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  5. 65
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  6. 144
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  7. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  8. 98
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  9. 51
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs
  10. 67
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs
  11. 51
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs
  12. 67
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs
  13. 47
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  14. 4
      src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs
  15. 10
      src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs
  16. 9
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  17. 1
      src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs
  18. 12
      src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs
  19. 48
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  20. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs

1
src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs

@ -5,7 +5,6 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;

59
src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs

@ -13,14 +13,11 @@ using System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{ {
public sealed class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, ISnapshotStore<AppState, Guid> public sealed partial class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository
{ {
public MongoAppRepository(IMongoDatabase database) public MongoAppRepository(IMongoDatabase database)
: base(database) : base(database)
@ -38,15 +35,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
} }
public async Task<Guid> FindAppIdByNameAsync(string name)
{
var appEntity =
await Collection.Find(x => x.Name == name).Only(x => x.Id)
.FirstOrDefaultAsync();
return appEntity != null ? Guid.Parse(appEntity["_id"].AsString) : Guid.Empty;
}
public async Task<IReadOnlyList<Guid>> QueryAppIdsAsync() public async Task<IReadOnlyList<Guid>> QueryAppIdsAsync()
{ {
var appEntities = var appEntities =
@ -65,50 +53,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
return appEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); return appEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
} }
public async Task<(AppState Value, long Version)> ReadAsync(Guid key) public async Task<Guid> FindAppIdByNameAsync(string name)
{ {
var existing = var appEntity =
await Collection.Find(x => x.Id == key) await Collection.Find(x => x.Name == name).Only(x => x.Id)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existing != null) return appEntity != null ? Guid.Parse(appEntity["_id"].AsString) : Guid.Empty;
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.UserIds, value.Contributors.Keys.ToArray())
.Set(x => x.Name, value.Name)
.Set(x => x.State, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key)
.Project<MongoAppEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex);
}
}
else
{
throw;
}
}
} }
} }
} }

67
src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository_SnapshotStore.cs

@ -0,0 +1,67 @@
// ==========================================================================
// MongoAppRepository_SnapshotStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed partial class MongoAppRepository : ISnapshotStore<AppState, Guid>
{
public async Task<(AppState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.UserIds, value.Contributors.Keys.ToArray())
.Set(x => x.Name, value.Name)
.Set(x => x.State, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key)
.Project<MongoAppEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

64
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -14,14 +14,12 @@ using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public sealed class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, ISnapshotStore<AssetState, Guid> public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository
{ {
public MongoAssetRepository(IMongoDatabase database) public MongoAssetRepository(IMongoDatabase database)
: base(database) : base(database)
@ -71,67 +69,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var find = Collection.Find(filter); var find = Collection.Find(filter);
var assetEntities = var assetItems = find.Skip(skip).Limit(take).SortByDescending(x => x.State.LastModified).ToListAsync();
Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.State.LastModified) var assetCount = find.CountAsync();
.ToListAsync();
var assetCount =
Collection.Find(filter)
.CountAsync();
await Task.WhenAll(assetEntities, assetCount); await Task.WhenAll(assetItems, assetCount);
return ResultList.Create<IAssetEntity>(assetEntities.Result.Select(x => x.State), assetCount.Result); return ResultList.Create<IAssetEntity>(assetItems.Result.Select(x => x.State), assetCount.Result);
} }
public async Task<IAssetEntity> FindAssetAsync(Guid id) public async Task<IAssetEntity> FindAssetAsync(Guid id)
{ {
var (state, etag) = await ReadAsync(id); var assetEntity =
await Collection.Find(x => x.Id == id)
return state;
}
public async Task<(AssetState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existingVersion != null) return assetEntity?.State;
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
} }
} }
} }

65
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs

@ -0,0 +1,65 @@
// ==========================================================================
// MongoAssetRepository_SnapshotStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed partial class MongoAssetRepository : ISnapshotStore<AssetState, Guid>
{
public async Task<(AssetState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

144
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -13,25 +13,17 @@ using System.Threading.Tasks;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public partial class MongoContentRepository : MongoRepositoryBase<MongoContentEntity>, public partial class MongoContentRepository : MongoRepositoryBase<MongoContentEntity>, IContentRepository
IEventConsumer,
IContentRepository,
ISnapshotStore<ContentState, Guid>
{ {
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
@ -71,92 +63,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText)); await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText));
} }
public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
{ {
var documentId = $"{key}_{newVersion}"; IFindFluent<MongoContentEntity, MongoContentEntity> find;
if (value.SchemaId == Guid.Empty)
{
return;
}
var schema = await appProvider.GetSchemaAsync(value.AppId, value.SchemaId, true);
if (schema == null)
{
throw new InvalidOperationException($"Cannot find schema {value.SchemaId}");
}
var idData = value.Data?.ToIdModel(schema.SchemaDef, true);
var document = SimpleMapper.Map(value, new MongoContentEntity
{
DocumentId = documentId,
DataText = idData?.ToFullText(),
DataByIds = idData,
IsLatest = !value.IsDeleted,
ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef),
});
try
{
await Collection.InsertOneAsync(document);
await Collection.UpdateManyAsync(x => x.Id == value.Id && x.Version < value.Version, Update.Set(x => x.IsLatest, false));
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == value.Id && x.IsLatest).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["vs"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key)
{
var contentEntity =
await Collection.Find(x => x.Id == key).SortByDescending(x => x.Version)
.FirstOrDefaultAsync();
if (contentEntity != null)
{
var schema = await appProvider.GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId, true);
if (schema == null)
{
throw new InvalidOperationException($"Cannot find schema {contentEntity.SchemaId}");
}
contentEntity?.ParseData(schema.SchemaDef);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try try
{ {
cursor = find = Collection.Find(odataQuery, schema.Id, schema.SchemaDef, status);
Collection
.Find(odataQuery, schema.Id, schema.SchemaDef, status)
.Take(odataQuery)
.Skip(odataQuery)
.Sort(odataQuery, schema.SchemaDef);
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
@ -167,56 +79,34 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
throw new ValidationException("This odata operation is not supported."); throw new ValidationException("This odata operation is not supported.");
} }
var contentEntities = await cursor.ToListAsync(); var contentItems = find.Take(odataQuery).Skip(odataQuery).Sort(odataQuery, schema.SchemaDef).ToListAsync();
var contentCount = find.CountAsync();
foreach (var entity in contentEntities)
{
entity.ParseData(schema.SchemaDef);
}
return contentEntities; await Task.WhenAll(contentItems, contentCount);
}
public async Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) foreach (var entity in contentItems.Result)
{ {
IFindFluent<MongoContentEntity, MongoContentEntity> cursor; entity.ParseData(schema.SchemaDef);
try
{
cursor = Collection.Find(odataQuery, schema.Id, schema.SchemaDef, status);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported.");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported.");
} }
return await cursor.CountAsync(); return ResultList.Create<IContentEntity>(contentItems.Result, contentCount.Result);
} }
public async Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids)
{ {
var contentsCount = var find = Collection.Find(x => ids.Contains(x.Id) && x.IsLatest);
await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest)
.CountAsync();
return contentsCount; var contentItems = find.ToListAsync();
} var contentCount = find.CountAsync();
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids) await Task.WhenAll(contentItems, contentCount);
{
var contentEntities =
await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest)
.ToListAsync();
foreach (var entity in contentEntities) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef); entity.ParseData(schema.SchemaDef);
} }
return contentEntities.OfType<IContentEntity>().ToList(); return ResultList.Create<IContentEntity>(contentItems.Result, contentCount.Result);
} }
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds) public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds)

2
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public partial class MongoContentRepository public partial class MongoContentRepository : IEventConsumer
{ {
public string Name public string Name
{ {

98
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -0,0 +1,98 @@
// ==========================================================================
// MongoContentRepository_SnapshotStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public partial class MongoContentRepository : ISnapshotStore<ContentState, Guid>
{
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key)
{
var contentEntity =
await Collection.Find(x => x.Id == key).SortByDescending(x => x.Version)
.FirstOrDefaultAsync();
if (contentEntity != null)
{
var schema = await appProvider.GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId, true);
if (schema == null)
{
throw new InvalidOperationException($"Cannot find schema {contentEntity.SchemaId}");
}
contentEntity?.ParseData(schema.SchemaDef);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion)
{
var documentId = $"{key}_{newVersion}";
if (value.SchemaId == Guid.Empty)
{
return;
}
var schema = await appProvider.GetSchemaAsync(value.AppId, value.SchemaId, true);
if (schema == null)
{
throw new InvalidOperationException($"Cannot find schema {value.SchemaId}");
}
var idData = value.Data?.ToIdModel(schema.SchemaDef, true);
var document = SimpleMapper.Map(value, new MongoContentEntity
{
DocumentId = documentId,
DataText = idData?.ToFullText(),
DataByIds = idData,
IsLatest = !value.IsDeleted,
ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef),
});
try
{
await Collection.InsertOneAsync(document);
await Collection.UpdateManyAsync(x => x.Id == value.Id && x.Version < value.Version, Update.Set(x => x.IsLatest, false));
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == value.Id && x.IsLatest).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["vs"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

51
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs

@ -12,14 +12,11 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
public sealed class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository, ISnapshotStore<RuleState, Guid> public sealed partial class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository
{ {
public MongoRuleRepository(IMongoDatabase database) public MongoRuleRepository(IMongoDatabase database)
: base(database) : base(database)
@ -37,20 +34,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsDeleted)); await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsDeleted));
} }
public async Task<(RuleState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId) public async Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId)
{ {
var ruleEntities = var ruleEntities =
@ -59,37 +42,5 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return ruleEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); return ruleEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
} }
public async Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.IsDeleted, value.IsDeleted)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
} }
} }

67
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs

@ -0,0 +1,67 @@
// ==========================================================================
// MongoRuleRepository_SnapshotStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed partial class MongoRuleRepository : ISnapshotStore<RuleState, Guid>
{
public async Task<(RuleState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.IsDeleted, value.IsDeleted)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

51
src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs

@ -12,14 +12,11 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{ {
public sealed class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository, ISnapshotStore<SchemaState, Guid> public sealed partial class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository
{ {
public MongoSchemaRepository(IMongoDatabase database) public MongoSchemaRepository(IMongoDatabase database)
: base(database) : base(database)
@ -37,20 +34,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
} }
public async Task<(SchemaState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId, string name) public async Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId, string name)
{ {
var schemaEntities = var schemaEntities =
@ -68,37 +51,5 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
} }
public async Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.Name, value.Name)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
} }
} }

67
src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs

@ -0,0 +1,67 @@
// ==========================================================================
// MongoSchemaRepository_SnapshotStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{
public sealed partial class MongoSchemaRepository : ISnapshotStore<SchemaState, Guid>
{
public async Task<(SchemaState Value, long Version)> ReadAsync(Guid key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.Name, value.Name)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

47
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -70,12 +70,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity)); throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity));
} }
content = TransformContent(user, schema, new List<IContentEntity> { content })[0]; content = TransformContent(user, schema, Enumerable.Repeat(content, 1)).FirstOrDefault();
return (schema, content); return (schema, content);
} }
public async Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) public async Task<(ISchemaEntity Schema, IResultList<IContentEntity> Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
Guard.NotNull(user, nameof(user)); Guard.NotNull(user, nameof(user));
@ -84,20 +84,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
var schema = await FindSchemaAsync(app, schemaIdOrName); var schema = await FindSchemaAsync(app, schemaIdOrName);
var parsedQuery = ParseQuery(app, query, schema); var parsedQuery = ParseQuery(app, query, schema);
var parsedStatus = ParseStatus(user, archived);
var status = ParseStatus(user, archived); var contents = await contentRepository.QueryAsync(app, schema, parsedStatus.ToArray(), parsedQuery);
var taskForItems = contentRepository.QueryAsync(app, schema, status.ToArray(), parsedQuery); return TransformContents(user, schema, contents);
var taskForCount = contentRepository.CountAsync(app, schema, status.ToArray(), parsedQuery);
await Task.WhenAll(taskForItems, taskForCount);
var list = TransformContent(user, schema, taskForItems.Result.ToList());
return (schema, taskForCount.Result, list);
} }
public async Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids) public async Task<(ISchemaEntity Schema, IResultList<IContentEntity> Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids)
{ {
Guard.NotNull(ids, nameof(ids)); Guard.NotNull(ids, nameof(ids));
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
@ -106,34 +100,41 @@ namespace Squidex.Domain.Apps.Entities.Contents
var schema = await FindSchemaAsync(app, schemaIdOrName); var schema = await FindSchemaAsync(app, schemaIdOrName);
var status = ParseStatus(user, archived); var parsedStatus = ParseStatus(user, archived);
var taskForItems = contentRepository.QueryAsync(app, schema, status.ToArray(), ids); var contents = await contentRepository.QueryAsync(app, schema, parsedStatus.ToArray(), ids);
var taskForCount = contentRepository.CountAsync(app, schema, status.ToArray(), ids);
await Task.WhenAll(taskForItems, taskForCount); return TransformContents(user, schema, contents);
}
var list = TransformContent(user, schema, taskForItems.Result.ToList()); private (ISchemaEntity Schema, IResultList<IContentEntity> Contents) TransformContents(ClaimsPrincipal user, ISchemaEntity schema, IResultList<IContentEntity> contents)
{
var transformed = TransformContent(user, schema, contents);
return (schema, taskForCount.Result, list); return (schema, ResultList.Create(transformed, contents.Total));
} }
private List<IContentEntity> TransformContent(ClaimsPrincipal user, ISchemaEntity schema, List<IContentEntity> contents) private IEnumerable<IContentEntity> TransformContent(ClaimsPrincipal user, ISchemaEntity schema, IEnumerable<IContentEntity> contents)
{ {
var scriptText = schema.ScriptQuery; var scriptText = schema.ScriptQuery;
if (!string.IsNullOrWhiteSpace(scriptText)) if (!string.IsNullOrWhiteSpace(scriptText))
{ {
for (var i = 0; i < contents.Count; i++) foreach (var content in contents)
{ {
var content = contents[i];
var contentData = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText); var contentData = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText);
var contentResult = SimpleMapper.Map(content, new Content());
contentResult.Data = contentData;
contents[i] = SimpleMapper.Map(content, new Content { Data = contentData }); yield return contentResult;
} }
} }
return contents; foreach (var content in contents)
{
yield return content;
}
} }
private ODataUriParser ParseQuery(IAppEntity app, string query, ISchemaEntity schema) private ODataUriParser ParseQuery(IAppEntity app, string query, ISchemaEntity schema)

4
src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs

@ -18,9 +18,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentQueryService public interface IContentQueryService
{ {
Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids); Task<(ISchemaEntity Schema, IResultList<IContentEntity> Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids);
Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); Task<(ISchemaEntity Schema, IResultList<IContentEntity> Contents)> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query);
Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any); Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any);

10
src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs

@ -95,14 +95,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
public async Task<IReadOnlyList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query) public async Task<IReadOnlyList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query)
{ {
var contents = await contentQuery.QueryWithCountAsync(app, schemaIdOrName, user, false, query); var result = await contentQuery.QueryAsync(app, schemaIdOrName, user, false, query);
foreach (var content in contents.Items) foreach (var content in result.Contents)
{ {
cachedContents[content.Id] = content; cachedContents[content.Id] = content;
} }
return contents.Items; return result.Contents;
} }
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids) public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids)
@ -132,9 +132,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (notLoadedContents.Count > 0) if (notLoadedContents.Count > 0)
{ {
var contents = await contentQuery.QueryWithCountAsync(app, schemaId.ToString(), user, false, notLoadedContents); var result = await contentQuery.QueryAsync(app, schemaId.ToString(), user, false, notLoadedContents);
foreach (var content in contents.Items) foreach (var content in result.Contents)
{ {
cachedContents[content.Id] = content; cachedContents[content.Id] = content;
} }

9
src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs

@ -13,21 +13,18 @@ using Microsoft.OData.UriParser;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Repositories namespace Squidex.Domain.Apps.Entities.Contents.Repositories
{ {
public interface IContentRepository public interface IContentRepository
{ {
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids); Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids);
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery); Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery);
Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds); Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds);
Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids);
Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery);
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id); Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id);
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version); Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version);

1
src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;

12
src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs

@ -86,21 +86,21 @@ namespace Squidex.Areas.Api.Controllers.Contents
var isFrontendClient = User.IsFrontendClient(); var isFrontendClient = User.IsFrontendClient();
var contents = var result =
idsList != null ? idsList != null ?
await contentQuery.QueryWithCountAsync(App, name, User, archived, idsList) : await contentQuery.QueryAsync(App, name, User, archived, idsList) :
await contentQuery.QueryWithCountAsync(App, name, User, archived, Request.QueryString.ToString()); await contentQuery.QueryAsync(App, name, User, archived, Request.QueryString.ToString());
var response = new AssetsDto var response = new AssetsDto
{ {
Total = contents.Total, Total = result.Contents.Total,
Items = contents.Items.Take(200).Select(item => Items = result.Contents.Take(200).Select(item =>
{ {
var itemModel = SimpleMapper.Map(item, new ContentDto()); var itemModel = SimpleMapper.Map(item, new ContentDto());
if (item.Data != null) if (item.Data != null)
{ {
itemModel.Data = item.Data.ToApiModel(contents.Schema.SchemaDef, App.LanguagesConfig, !isFrontendClient); itemModel.Data = item.Data.ToApiModel(result.Schema.SchemaDef, App.LanguagesConfig, !isFrontendClient);
} }
return itemModel; return itemModel;

48
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
@ -36,8 +37,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly Guid schemaId = Guid.NewGuid(); private readonly Guid schemaId = Guid.NewGuid();
private readonly Guid contentId = Guid.NewGuid(); private readonly Guid contentId = Guid.NewGuid();
private readonly string appName = "my-app"; private readonly string appName = "my-app";
private readonly NamedContentData data = new NamedContentData(); private readonly NamedContentData contentData = new NamedContentData();
private readonly NamedContentData transformedData = new NamedContentData(); private readonly NamedContentData contentTransformed = new NamedContentData();
private readonly ClaimsPrincipal user; private readonly ClaimsPrincipal user;
private readonly ClaimsIdentity identity = new ClaimsIdentity(); private readonly ClaimsIdentity identity = new ClaimsIdentity();
private readonly EdmModelBuilder modelBuilder = A.Fake<EdmModelBuilder>(); private readonly EdmModelBuilder modelBuilder = A.Fake<EdmModelBuilder>();
@ -51,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.Name).Returns(appName);
A.CallTo(() => content.Id).Returns(contentId); A.CallTo(() => content.Id).Returns(contentId);
A.CallTo(() => content.Data).Returns(data); A.CallTo(() => content.Data).Returns(contentData);
A.CallTo(() => content.Status).Returns(Status.Published); A.CallTo(() => content.Status).Returns(Status.Published);
sut = new ContentQueryService(contentRepository, appProvider, scriptEngine, modelBuilder); sut = new ContentQueryService(contentRepository, appProvider, scriptEngine, modelBuilder);
@ -99,13 +100,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => schema.ScriptQuery) A.CallTo(() => schema.ScriptQuery)
.Returns("<script-query>"); .Returns("<script-query>");
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, data)), "<query-script>")) A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "<query-script>"))
.Returns(transformedData); .Returns(contentTransformed);
var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId); var result = await sut.FindContentAsync(app, schemaId.ToString(), user, contentId);
Assert.Equal(schema, result.Schema); Assert.Equal(schema, result.Schema);
Assert.Equal(data, result.Content.Data);
Assert.Equal(contentTransformed, result.Content.Data);
Assert.Equal(content.Id, result.Content.Id); Assert.Equal(content.Id, result.Content.Id);
} }
@ -157,12 +159,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
SetupFakeWithOdataQuery(status); SetupFakeWithOdataQuery(status);
SetupFakeWithScripting(); SetupFakeWithScripting();
var result = await sut.QueryWithCountAsync(app, schemaId.ToString(), user, archive, string.Empty); var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, string.Empty);
Assert.Equal(123, result.Total);
Assert.Equal(schema, result.Schema); Assert.Equal(schema, result.Schema);
Assert.Equal(data, result.Items[0].Data);
Assert.Equal(content.Id, result.Items[0].Id); Assert.Equal(contentData, result.Contents[0].Data);
Assert.Equal(content.Id, result.Contents[0].Id);
Assert.Equal(123, result.Contents.Total);
} }
private async Task TestManyIdRequest(bool isFrontend, bool archive, HashSet<Guid> ids, params Status[] status) private async Task TestManyIdRequest(bool isFrontend, bool archive, HashSet<Guid> ids, params Status[] status)
@ -172,12 +176,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
SetupFakeWithIdQuery(status, ids); SetupFakeWithIdQuery(status, ids);
SetupFakeWithScripting(); SetupFakeWithScripting();
var result = await sut.QueryWithCountAsync(app, schemaId.ToString(), user, archive, ids); var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, ids);
Assert.Equal(123, result.Total);
Assert.Equal(schema, result.Schema); Assert.Equal(schema, result.Schema);
Assert.Equal(data, result.Items[0].Data);
Assert.Equal(content.Id, result.Items[0].Id); Assert.Equal(contentData, result.Contents[0].Data);
Assert.Equal(content.Id, result.Contents[0].Id);
Assert.Equal(123, result.Contents.Total);
} }
private void SetupClaims(bool isFrontend) private void SetupClaims(bool isFrontend)
@ -192,20 +198,18 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false))
.Returns(schema); .Returns(schema);
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), ids)) A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), ids))
.Returns(new List<IContentEntity> { content }); .Returns(ResultList.Create(Enumerable.Repeat(content, 1), 123));
A.CallTo(() => contentRepository.CountAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), ids))
.Returns(123);
} }
private void SetupFakeWithOdataQuery(Status[] status) private void SetupFakeWithOdataQuery(Status[] status)
{ {
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false))
.Returns(schema); .Returns(schema);
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), A<ODataUriParser>.Ignored)) A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), A<ODataUriParser>.Ignored))
.Returns(new List<IContentEntity> { content }); .Returns(ResultList.Create(Enumerable.Repeat(content, 1), 123));
A.CallTo(() => contentRepository.CountAsync(app, schema, A<Status[]>.That.IsSameSequenceAs(status), A<ODataUriParser>.Ignored))
.Returns(123);
} }
private void SetupFakeWithScripting() private void SetupFakeWithScripting()
@ -213,8 +217,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => schema.ScriptQuery) A.CallTo(() => schema.ScriptQuery)
.Returns("<script-query>"); .Returns("<script-query>");
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, data)), "<query-script>")) A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == contentId && ReferenceEquals(x.Data, contentData)), "<query-script>"))
.Returns(transformedData); .Returns(contentTransformed);
} }
} }
} }

8
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs

@ -278,8 +278,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var contents = new List<IContentEntity> { content }; var contents = new List<IContentEntity> { content };
A.CallTo(() => contentQuery.QueryWithCountAsync(app, schema.Id.ToString(), user, false, "?$top=30&$skip=5")) A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, "?$top=30&$skip=5"))
.Returns((schema, 0L, (IReadOnlyList<IContentEntity>)contents)); .Returns((schema, ResultList.Create(contents, 0)));
var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query });
@ -485,8 +485,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any))
.Returns((schema, content)); .Returns((schema, content));
A.CallTo(() => contentQuery.QueryWithCountAsync(app, schema.Id.ToString(), user, false, A<HashSet<Guid>>.That.Matches(x => x.Contains(contentRefId)))) A.CallTo(() => contentQuery.QueryAsync(app, schema.Id.ToString(), user, false, A<HashSet<Guid>>.That.Matches(x => x.Contains(contentRefId))))
.Returns((schema, 0L, (IReadOnlyList<IContentEntity>)refContents)); .Returns((schema, ResultList.Create(refContents, 0)));
var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query });

Loading…
Cancel
Save