Browse Source

Created synced object

pull/206/head
Sebastian Stehle 9 years ago
parent
commit
3334d8c71a
  1. 15
      Squidex.sln
  2. 34
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs
  3. 85
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  4. 30
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  5. 140
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  6. 6
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs
  7. 19
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs
  8. 8
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs
  9. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  10. 11
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  11. 29
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  12. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs
  13. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs
  14. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs
  15. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  16. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs
  17. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs
  18. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs
  19. 3
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs
  20. 27
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs
  21. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs
  22. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs
  23. 30
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs
  24. 6
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs
  25. 30
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  26. 85
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs
  27. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  28. 66
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  29. 18
      src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs
  30. 22
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  31. 19
      src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs
  32. 21
      src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs
  33. 75
      src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs
  34. 100
      src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs
  35. 64
      src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs
  36. 160
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  37. 6
      src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs
  38. 47
      src/Squidex.Infrastructure/Commands/AggregateHandler.cs
  39. 20
      src/Squidex.Infrastructure/Commands/CommandExtensions.cs
  40. 4
      src/Squidex.Infrastructure/Commands/IAggregateHandler.cs
  41. 2
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs
  42. 4
      src/Squidex.Infrastructure/States/IStateFactory.cs
  43. 4
      src/Squidex.Infrastructure/States/StateFactory.cs
  44. 74
      src/Squidex.Infrastructure/Tasks/AsyncLock.cs
  45. 37
      src/Squidex.Infrastructure/Tasks/AsyncLockPool.cs

15
Squidex.sln

@ -69,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entitie
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entities.Tests", "tests\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj", "{AA003372-CD8D-4DBC-962C-F61E0C93CF05}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entities.MongoDb", "src\Squidex.Domain.Apps.Entities.MongoDb\Squidex.Domain.Apps.Entities.MongoDb.csproj", "{7DA5B308-D950-4496-93D5-21D6C4D91644}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -343,6 +345,18 @@ Global
{AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x64.Build.0 = Release|Any CPU
{AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x86.ActiveCfg = Release|Any CPU
{AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x86.Build.0 = Release|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x64.ActiveCfg = Debug|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x64.Build.0 = Debug|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x86.ActiveCfg = Debug|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x86.Build.0 = Debug|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|Any CPU.Build.0 = Release|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x64.ActiveCfg = Release|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x64.Build.0 = Release|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x86.ActiveCfg = Release|Any CPU
{7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -374,6 +388,7 @@ Global
{6B3F75B6-5888-468E-BA4F-4FC725DAEF31} = {C9809D59-6665-471E-AD87-5AC624C65892}
{79FEF326-CA5E-4698-B2BA-C16A4580B4D5} = {C9809D59-6665-471E-AD87-5AC624C65892}
{AA003372-CD8D-4DBC-962C-F61E0C93CF05} = {C9809D59-6665-471E-AD87-5AC624C65892}
{7DA5B308-D950-4496-93D5-21D6C4D91644} = {C9809D59-6665-471E-AD87-5AC624C65892}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {02F2E872-3141-44F5-BD6A-33CD84E9FE08}

34
src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs

@ -0,0 +1,34 @@
// ==========================================================================
// MongoAppEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Apps.State;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed class MongoAppEntity
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
[BsonElement]
[BsonRequired]
public AppState State { get; set; }
[BsonElement]
[BsonRequired]
public int Version { get; set; }
[BsonElement]
[BsonRequired]
public string[] UserIds { get; set; }
}
}

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

@ -0,0 +1,85 @@
// ==========================================================================
// MongoAppRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, ISnapshotStore<AppState>
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{
return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.UserIds));
}
public async Task<(AppState Value, long Version)> ReadAsync(string key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, -1);
}
public async Task<IReadOnlyList<string>> QueryUserAppNamesAsync(string userId)
{
var appEntities =
await Collection.Find(x => x.UserIds.Contains(userId)).Project<MongoAppEntity>(Projection.Include(x => x.Id)).ToListAsync();
return appEntities.Select(x => x.Id).ToList();
}
public async Task WriteAsync(string 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.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;
}
}
}
}
}

30
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs

@ -0,0 +1,30 @@
// ==========================================================================
// MongoAssetEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Assets.State;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed class MongoAssetEntity
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
[BsonElement]
[BsonRequired]
public AssetState State { get; set; }
[BsonElement]
[BsonRequired]
public int Version { get; set; }
}
}

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

@ -0,0 +1,140 @@
// ==========================================================================
// MongoAssetRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, ISnapshotStore<AssetState>
{
public MongoAssetRepository(IMongoDatabase database)
: base(database)
{
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection)
{
return collection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.State.AppId)
.Ascending(x => x.State.FileName)
.Ascending(x => x.State.MimeType)
.Descending(x => x.State.LastModified));
}
public async Task<(AssetState Value, long Version)> ReadAsync(string key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, -1);
}
public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0)
{
var filter = CreateFilter(appId, mimeTypes, ids, query);
var assetEntities =
await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.State.LastModified)
.ToListAsync();
return assetEntities.OfType<IAssetEntity>().ToList();
}
public async Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null)
{
var filter = CreateFilter(appId, mimeTypes, ids, query);
var assetsCount =
await Collection.Find(filter)
.CountAsync();
return assetsCount;
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
{
var (state, etag) = await ReadAsync(id.ToString());
return state;
}
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query)
{
var filters = new List<FilterDefinition<MongoAssetEntity>>
{
Filter.Eq(x => x.State.AppId, appId)
};
if (ids != null && ids.Count > 0)
{
filters.Add(Filter.In(x => x.Id, ids.Select(x => x.ToString())));
}
if (mimeTypes != null && mimeTypes.Count > 0)
{
filters.Add(Filter.In(x => x.State.MimeType, mimeTypes));
}
if (!string.IsNullOrWhiteSpace(query))
{
filters.Add(Filter.Regex(x => x.State.FileName, new BsonRegularExpression(query, "i")));
}
var filter = Filter.And(filters);
return filter;
}
public async Task WriteAsync(string 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)
.Project<MongoAssetEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

6
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs

@ -9,9 +9,9 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Domain.Apps.Entities.Assets;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed class MongoAssetStatsEntity : IAssetStatsEntity
{
@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
public Guid AssetId { get; set; }
[BsonRequired]
[BsonElement]

19
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs

@ -11,12 +11,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public partial class MongoAssetStatsRepository : MongoRepositoryBase<MongoAssetStatsEntity>, IAssetStatsRepository, IAssetEventConsumer
{
@ -30,17 +30,16 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
return "Projections_AssetStats";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.Date)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Date)));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Ascending(x => x.Date));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Descending(x => x.Date));
}
public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate)
{
var originalSizesEntities =
await Collection.Find(x => x.AppId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date)
await Collection.Find(x => x.AssetId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date)
.ToListAsync();
var enrichedSizes = new List<MongoAssetStatsEntity>();
@ -64,7 +63,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
if (previousSize < 0)
{
var firstBeforeRangeEntity =
await Collection.Find(x => x.AppId == appId && x.Date < fromDate).SortByDescending(x => x.Date)
await Collection.Find(x => x.AssetId == appId && x.Date < fromDate).SortByDescending(x => x.Date)
.FirstOrDefaultAsync();
previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L;
@ -88,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
public async Task<long> GetTotalSizeAsync(Guid appId)
{
var totalSizeEntity =
await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date)
await Collection.Find(x => x.AssetId == appId).SortByDescending(x => x.Date)
.FirstOrDefaultAsync();
return totalSizeEntity?.TotalSize ?? 0;

8
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs

@ -13,12 +13,10 @@ using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public partial class MongoAssetStatsRepository
{
private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true };
public string Name
{
get { return GetType().Name; }
@ -60,14 +58,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
if (assetStatsEntity == null)
{
var lastEntity =
await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date)
await Collection.Find(x => x.AssetId == appId).SortByDescending(x => x.Date)
.FirstOrDefaultAsync();
assetStatsEntity = new MongoAssetStatsEntity
{
Id = id,
Date = date,
AppId = appId,
AssetId = appId,
TotalSize = lastEntity?.TotalSize ?? 0,
TotalCount = lastEntity?.TotalCount ?? 0
};

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs

@ -16,7 +16,7 @@ using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public static class Extensions
{

11
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -13,18 +13,13 @@ using MongoDB.Bson.Serialization.Attributes;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public sealed class MongoContentEntity :
IContentEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy,
IUpdateableEntityWithAppRef
public sealed class MongoContentEntity : IContentEntity
{
private NamedContentData data;

29
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -14,17 +14,16 @@ using Microsoft.OData.UriParser;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public partial class MongoContentRepository : IContentRepository, IEventConsumer
public class MongoContentRepository : IContentRepository
{
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
@ -189,5 +188,19 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
await action(collection, schema);
}
private Task ForAppIdAsync(Guid appId, Func<IMongoCollection<MongoContentEntity>, Task> action)
{
var collection = GetCollection(appId);
return action(collection);
}
private IMongoCollection<MongoContentEntity> GetCollection(Guid appId)
{
var name = $"{Prefix}{appId}";
return database.GetCollection<MongoContentEntity>(name);
}
}
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/ConstantVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs

@ -12,7 +12,7 @@ using Microsoft.OData.UriParser;
using NodaTime;
using NodaTime.Text;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public sealed class ConstantVisitor : QueryNodeVisitor<object>
{

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterBuilder.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs

@ -12,7 +12,7 @@ using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public static class FilterBuilder
{

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs

@ -13,7 +13,7 @@ using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public class FilterVisitor : QueryNodeVisitor<FilterDefinition<MongoContentEntity>>
{

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs

@ -14,7 +14,7 @@ using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public static class FindExtensions
{

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs

@ -14,7 +14,7 @@ using MongoDB.Driver;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public sealed class PropertyVisitor : QueryNodeVisitor<ImmutableList<string>>
{

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs

@ -9,7 +9,7 @@
using System;
using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public class SearchTermVisitor : QueryNodeVisitor<string>
{

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SortBuilder.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs

@ -11,7 +11,7 @@ using Microsoft.OData.UriParser;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public static class SortBuilder
{

3
src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs

@ -12,11 +12,12 @@ using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.History
namespace Squidex.Domain.Apps.Entities.MongoDb.History
{
public sealed class MongoHistoryEventEntity : MongoEntity,
IEntity,
IEntityWithAppRef,
IUpdateableEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithAppRef

27
src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs

@ -11,13 +11,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.History.Repositories;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Read.History.Repositories;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.History
namespace Squidex.Domain.Apps.Entities.MongoDb.History
{
public class MongoHistoryEventRepository : MongoRepositoryBase<MongoHistoryEventEntity>, IHistoryEventRepository, IEventConsumer
{
@ -53,23 +53,22 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
return "Projections_History";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoHistoryEventEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoHistoryEventEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.AppId)
.Ascending(x => x.Channel)
.Descending(x => x.Created)
.Descending(x => x.Version)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) }));
await collection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.AppId)
.Ascending(x => x.Channel)
.Descending(x => x.Created)
.Descending(x => x.Version));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) });
}
public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count)
{
var historyEventEntities =
await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix)
.SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count)
await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix).SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count)
.ToListAsync();
return historyEventEntities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList();

4
src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs → src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs

@ -9,12 +9,12 @@
using System;
using System.Collections.Generic;
using NodaTime;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Infrastructure;
#pragma warning disable RECS0029 // Warns about property or indexer setters and event adders or removers that do not use the value parameter
namespace Squidex.Domain.Apps.Read.MongoDb.History
namespace Squidex.Domain.Apps.Entities.MongoDb.History
{
internal sealed class ParsedHistoryEvent : IHistoryEventEntity
{

2
src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs → src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb
namespace Squidex.Domain.Apps.Entities.MongoDb
{
public static class MongoCollectionExtensions
{

30
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs

@ -0,0 +1,30 @@
// ==========================================================================
// MongoRuleEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Rules.State;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed class MongoRuleEntity
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
[BsonElement]
[BsonRequired]
public RuleState State { get; set; }
[BsonElement]
[BsonRequired]
public int Version { get; set; }
}
}

6
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs

@ -11,16 +11,16 @@ using MongoDB.Bson.Serialization.Attributes;
using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Rules
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed class MongoRuleEventEntity : MongoEntity, IRuleEventEntity
{
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
public Guid AssetId { get; set; }
[BsonRequired]
[BsonElement]

30
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -14,12 +14,12 @@ using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Read.MongoDb.Rules
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed class MongoRuleEventRepository : MongoRepositoryBase<MongoRuleEventEntity>, IRuleEventRepository
{
@ -33,12 +33,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
return "RuleEvents";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Created)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero }));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Descending(x => x.Created));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero });
}
public Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken))
@ -49,7 +48,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public async Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20)
{
var ruleEventEntities =
await Collection.Find(x => x.AppId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created)
await Collection.Find(x => x.AssetId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created)
.ToListAsync();
return ruleEventEntities;
@ -66,7 +65,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public async Task<int> CountByAppAsync(Guid appId)
{
return (int)await Collection.CountAsync(x => x.AppId == appId);
return (int)await Collection.CountAsync(x => x.AssetId == appId);
}
public Task EnqueueAsync(Guid id, Instant nextAttempt)
@ -84,11 +83,12 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextAttempt)
{
return Collection.UpdateOneAsync(x => x.Id == jobId,
Update.Set(x => x.Result, result)
.Set(x => x.LastDump, dump)
.Set(x => x.JobResult, jobResult)
.Set(x => x.NextAttempt, nextAttempt)
.Inc(x => x.NumCalls, 1));
Update
.Set(x => x.Result, result)
.Set(x => x.LastDump, dump)
.Set(x => x.JobResult, jobResult)
.Set(x => x.NextAttempt, nextAttempt)
.Inc(x => x.NumCalls, 1));
}
}
}

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

@ -0,0 +1,85 @@
// ==========================================================================
// MongoRuleRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository, ISnapshotStore<RuleState>
{
public MongoRuleRepository(IMongoDatabase database)
: base(database)
{
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection)
{
return collection.Indexes.CreateOneAsync(Index.Ascending(x => x.State.AppId));
}
public async Task<(RuleState Value, long Version)> ReadAsync(string key)
{
var existing =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (existing != null)
{
return (existing.State, existing.Version);
}
return (null, -1);
}
public async Task<IReadOnlyList<string>> QueryRuleIdsAsync(Guid appId)
{
var ruleEntities =
await Collection.Find(x => x.State.AppId == appId).Project<MongoRuleEntity>(Projection.Include(x => x.Id)).ToListAsync();
return ruleEntities.Select(x => x.Id).ToList();
}
public async Task WriteAsync(string 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.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key)
.Project<MongoRuleEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj → src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -12,7 +12,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" />
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
<ProjectReference Include="..\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Read\Squidex.Domain.Apps.Read.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />

66
src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -0,0 +1,66 @@
// ==========================================================================
// AppProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities
{
public sealed class AppProvider : IAppProvider
{
private readonly IStateFactory factory;
public AppProvider(IStateFactory factory)
{
Guard.NotNull(factory, nameof(factory));
this.factory = factory;
}
public Task<IAppEntity> GetAppAsync(string appName)
{
return null;
}
public Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id)
{
return null;
}
public Task<List<IRuleEntity>> GetRulesAsync(string appName)
{
return null;
}
public Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false)
{
return null;
}
public Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false)
{
return null;
}
public Task<List<ISchemaEntity>> GetSchemasAsync(string appName)
{
return null;
}
public Task<List<IAppEntity>> GetUserApps(string userId)
{
return null;
}
}
}

18
src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs

@ -0,0 +1,18 @@
// ==========================================================================
// IAppRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Apps.Repositories
{
public interface IAppRepository
{
Task<IReadOnlyList<string>> QueryUserAppNamesAsync(string userId);
}
}

22
src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs

@ -0,0 +1,22 @@
// ==========================================================================
// ContentState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents.State
{
public sealed class ContentState : DomainObjectState<ContentState>
{
[JsonProperty]
public IdContentData Data { get; set; }
[JsonProperty]
public string Status { get; set; }
}
}

19
src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs

@ -0,0 +1,19 @@
// ==========================================================================
// IRuleRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Rules.Repositories
{
public interface IRuleRepository
{
Task<IReadOnlyList<string>> QueryRuleIdsAsync(Guid appId);
}
}

21
src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs

@ -0,0 +1,21 @@
// ==========================================================================
// ISchemaRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Schemas.Repositories
{
public interface ISchemaRepository
{
Task<string> FindSchemaNameAsync(Guid schemaId);
Task<IReadOnlyList<string>> QuerySchemaNamesAsync(Guid appId);
}
}

75
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs

@ -1,75 +0,0 @@
// ==========================================================================
// MongoAssetEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets
{
public sealed class MongoAssetEntity :
MongoEntity,
IAssetEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy,
IUpdateableEntityWithAppRef
{
[BsonRequired]
[BsonElement]
public string MimeType { get; set; }
[BsonRequired]
[BsonElement]
public string FileName { get; set; }
[BsonRequired]
[BsonElement]
public long FileSize { get; set; }
[BsonRequired]
[BsonElement]
public long FileVersion { get; set; }
[BsonRequired]
[BsonElement]
public bool IsImage { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
public int? PixelWidth { get; set; }
[BsonRequired]
[BsonElement]
public int? PixelHeight { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
[BsonRequired]
[BsonElement]
public RefToken CreatedBy { get; set; }
[BsonRequired]
[BsonElement]
public RefToken LastModifiedBy { get; set; }
Guid IAssetInfo.AssetId
{
get { return Id; }
}
}
}

100
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs

@ -1,100 +0,0 @@
// ==========================================================================
// MongoAssetRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets
{
public partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, IAssetEventConsumer
{
public MongoAssetRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "Projections_Assets";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection)
{
return collection.Indexes.CreateOneAsync(
Index.Ascending(x => x.AppId)
.Ascending(x => x.FileName)
.Ascending(x => x.MimeType)
.Descending(x => x.LastModified));
}
public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0)
{
var filter = CreateFilter(appId, mimeTypes, ids, query);
var assetEntities =
await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified)
.ToListAsync();
return assetEntities.OfType<IAssetEntity>().ToList();
}
public async Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null)
{
var filter = CreateFilter(appId, mimeTypes, ids, query);
var assetsCount =
await Collection.Find(filter)
.CountAsync();
return assetsCount;
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
{
var assetEntity =
await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return assetEntity;
}
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query)
{
var filters = new List<FilterDefinition<MongoAssetEntity>>
{
Filter.Eq(x => x.AppId, appId)
};
if (ids != null && ids.Count > 0)
{
filters.Add(Filter.In(x => x.Id, ids));
}
if (mimeTypes != null && mimeTypes.Count > 0)
{
filters.Add(Filter.In(x => x.MimeType, mimeTypes));
}
if (!string.IsNullOrWhiteSpace(query))
{
filters.Add(Filter.Regex(x => x.FileName, new BsonRegularExpression(query, "i")));
}
var filter = Filter.And(filters);
return filter;
}
}
}

64
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs

@ -1,64 +0,0 @@
// ==========================================================================
// MongoAssetRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets
{
public partial class MongoAssetRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^asset-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(AssetCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
});
}
protected Task On(AssetUpdated @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
});
}
protected Task On(AssetRenamed @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
});
}
protected Task On(AssetDeleted @event, EnvelopeHeaders headers)
{
return Collection.DeleteOneAsync(x => x.Id == @event.AssetId);
}
}
}

160
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -1,160 +0,0 @@
// ==========================================================================
// MongoContentRepository_EventHandling.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.Events.Apps;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{
public partial class MongoContentRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^(content-)|(app-)|(asset-)"; }
}
public async Task ClearAsync()
{
using (var collections = await database.ListCollectionsAsync())
{
while (await collections.MoveNextAsync())
{
foreach (var collection in collections.Current)
{
var name = collection["name"].ToString();
if (name.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase))
{
await database.DropCollectionAsync(name);
}
}
}
}
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(AppCreated @event, EnvelopeHeaders headers)
{
return ForAppIdAsync(@event.AppId.Id, async collection =>
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaId).Descending(x => x.LastModified));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Status));
await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText));
});
}
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) =>
{
return collection.CreateAsync(@event, headers, content =>
{
content.SchemaId = @event.SchemaId.Id;
SimpleMapper.Map(@event, content);
var idData = @event.Data?.ToIdModel(schema.SchemaDef, true);
content.DataText = idData?.ToFullText();
content.IdData = idData;
content.ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef);
});
});
}
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) =>
{
var idData = @event.Data?.ToIdModel(schema.SchemaDef, true);
return collection.UpdateOneAsync(
Filter.Eq(x => x.Id, @event.ContentId),
Update
.Set(x => x.DataText, idData.ToFullText())
.Set(x => x.IdData, idData)
.Set(x => x.ReferencedIds, idData.ToReferencedIds(schema.SchemaDef))
.Set(x => x.LastModified, headers.Timestamp())
.Set(x => x.LastModifiedBy, @event.Actor)
.Set(x => x.Version, headers.EventStreamNumber()));
});
}
protected Task On(ContentStatusChanged @event, EnvelopeHeaders headers)
{
return ForAppIdAsync(@event.AppId.Id, collection =>
{
return collection.UpdateOneAsync(
Filter.Eq(x => x.Id, @event.ContentId),
Update
.Set(x => x.Status, @event.Status)
.Set(x => x.LastModified, headers.Timestamp())
.Set(x => x.LastModifiedBy, @event.Actor)
.Set(x => x.Version, headers.EventStreamNumber()));
});
}
protected Task On(AssetDeleted @event, EnvelopeHeaders headers)
{
return ForAppIdAsync(@event.AppId.Id, collection =>
{
return collection.UpdateManyAsync(
Filter.And(
Filter.AnyEq(x => x.ReferencedIds, @event.AssetId),
Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.AssetId)),
Update.AddToSet(x => x.ReferencedIdsDeleted, @event.AssetId));
});
}
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
{
return ForAppIdAsync(@event.AppId.Id, async collection =>
{
await collection.UpdateManyAsync(
Filter.And(
Filter.AnyEq(x => x.ReferencedIds, @event.ContentId),
Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.ContentId)),
Update.AddToSet(x => x.ReferencedIdsDeleted, @event.ContentId));
await collection.DeleteOneAsync(x => x.Id == @event.ContentId);
});
}
private Task ForAppIdAsync(Guid appId, Func<IMongoCollection<MongoContentEntity>, Task> action)
{
var collection = GetCollection(appId);
return action(collection);
}
private IMongoCollection<MongoContentEntity> GetCollection(Guid appId)
{
var name = $"{Prefix}{appId}";
return database.GetCollection<MongoContentEntity>(name);
}
}
}

6
src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs

@ -48,11 +48,7 @@ namespace Squidex.Infrastructure.States
{
try
{
await Collection.UpdateOneAsync(
Filter.And(
Filter.Eq(x => x.Id, key),
Filter.Eq(x => x.Version, oldVersion)
),
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.Doc, value)
.Set(x => x.Version, newVersion),

47
src/Squidex.Infrastructure/Commands/AggregateHandler.cs

@ -10,11 +10,13 @@ using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Commands
{
public sealed class AggregateHandler : IAggregateHandler
{
private readonly AsyncLockPool lockPool = new AsyncLockPool(10000);
private readonly IStateFactory stateFactory;
private readonly ISemanticLog log;
private readonly IServiceProvider serviceProvider;
@ -45,13 +47,27 @@ namespace Squidex.Infrastructure.Commands
return InvokeAsync(context, updater, true);
}
public Task<T> CreateSyncedAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IDomainObject
{
Guard.NotNull(creator, nameof(creator));
return InvokeSyncedAsync(context, creator, false);
}
public Task<T> UpdateSyncedAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IDomainObject
{
Guard.NotNull(updater, nameof(updater));
return InvokeSyncedAsync(context, updater, true);
}
private async Task<T> InvokeAsync<T>(CommandContext context, Func<T, Task> handler, bool isUpdate) where T : class, IDomainObject
{
Guard.NotNull(context, nameof(context));
var domainObjectCommand = GetCommand(context);
var domainObjectId = domainObjectCommand.AggregateId;
var domainObject = await stateFactory.GetDetachedAsync<T>(domainObjectId.ToString());
var domainObject = await stateFactory.CreateAsync<T>(domainObjectId.ToString());
await domainObject.WriteAsync(log);
@ -70,6 +86,35 @@ namespace Squidex.Infrastructure.Commands
return domainObject;
}
private async Task<T> InvokeSyncedAsync<T>(CommandContext context, Func<T, Task> handler, bool isUpdate) where T : class, IDomainObject
{
Guard.NotNull(context, nameof(context));
var domainObjectCommand = GetCommand(context);
var domainObjectId = domainObjectCommand.AggregateId;
using (await lockPool.LockAsync(Tuple.Create(typeof(T), domainObjectId)))
{
var domainObject = await stateFactory.GetSingleAsync<T>(domainObjectId.ToString());
await domainObject.WriteAsync(log);
if (!context.IsCompleted)
{
if (isUpdate)
{
context.Complete(new EntitySavedResult(domainObject.Version));
}
else
{
context.Complete(EntityCreatedResult.Create(domainObjectId, domainObject.Version));
}
}
return domainObject;
}
}
private static IAggregateCommand GetCommand(CommandContext context)
{
if (!(context.Command is IAggregateCommand command))

20
src/Squidex.Infrastructure/Commands/CommandExtensions.cs

@ -34,6 +34,26 @@ namespace Squidex.Infrastructure.Commands
});
}
public static Task CreateSyncedAsync<T>(this IAggregateHandler handler, CommandContext context, Action<T> creator) where T : class, IDomainObject
{
return handler.CreateSyncedAsync<T>(context, x =>
{
creator(x);
return TaskHelper.Done;
});
}
public static Task UpdateSyncedAsync<T>(this IAggregateHandler handler, CommandContext context, Action<T> updater) where T : class, IDomainObject
{
return handler.UpdateSyncedAsync<T>(context, x =>
{
updater(x);
return TaskHelper.Done;
});
}
public static Task HandleAsync(this ICommandMiddleware commandMiddleware, CommandContext context)
{
return commandMiddleware.HandleAsync(context, () => TaskHelper.Done);

4
src/Squidex.Infrastructure/Commands/IAggregateHandler.cs

@ -15,6 +15,10 @@ namespace Squidex.Infrastructure.Commands
{
Task<T> CreateAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IDomainObject;
Task<T> CreateSyncedAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IDomainObject;
Task<T> UpdateAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IDomainObject;
Task<T> UpdateSyncedAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IDomainObject;
}
}

2
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs

@ -39,7 +39,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
foreach (var consumer in consumers)
{
var actor = factory.GetDetachedAsync<EventConsumerGrain>(consumer.Name).Result;
var actor = factory.CreateAsync<EventConsumerGrain>(consumer.Name).Result;
actors[consumer.Name] = actor;
actor.Activate(consumer);

4
src/Squidex.Infrastructure/States/IStateFactory.cs

@ -12,8 +12,8 @@ namespace Squidex.Infrastructure.States
{
public interface IStateFactory
{
Task<T> GetSynchronizedAsync<T>(string key) where T : IStatefulObject;
Task<T> GetSingleAsync<T>(string key) where T : IStatefulObject;
Task<T> GetDetachedAsync<T>(string key) where T : IStatefulObject;
Task<T> CreateAsync<T>(string key) where T : IStatefulObject;
}
}

4
src/Squidex.Infrastructure/States/StateFactory.cs

@ -81,7 +81,7 @@ namespace Squidex.Infrastructure.States
});
}
public async Task<T> GetDetachedAsync<T>(string key) where T : IStatefulObject
public async Task<T> CreateAsync<T>(string key) where T : IStatefulObject
{
Guard.NotNull(key, nameof(key));
@ -93,7 +93,7 @@ namespace Squidex.Infrastructure.States
return state;
}
public Task<T> GetSynchronizedAsync<T>(string key) where T : IStatefulObject
public Task<T> GetSingleAsync<T>(string key) where T : IStatefulObject
{
Guard.NotNull(key, nameof(key));

74
src/Squidex.Infrastructure/Tasks/AsyncLock.cs

@ -0,0 +1,74 @@
// ==========================================================================
// AsyncLock.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading;
using System.Threading.Tasks;
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
namespace Squidex.Infrastructure.Tasks
{
public sealed class AsyncLock
{
private readonly SemaphoreSlim semaphore;
public AsyncLock()
{
semaphore = new SemaphoreSlim(1);
}
public Task<IDisposable> LockAsync()
{
Task wait = semaphore.WaitAsync();
if (wait.IsCompleted)
{
return Task.FromResult((IDisposable)new LockReleaser(this));
}
else
{
return wait.ContinueWith(x => (IDisposable)new LockReleaser(this),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
}
private class LockReleaser : IDisposable
{
private AsyncLock target;
internal LockReleaser(AsyncLock target)
{
this.target = target;
}
public void Dispose()
{
AsyncLock current = target;
if (current == null)
{
return;
}
target = null;
try
{
current.semaphore.Release();
}
catch
{
// just ignore the Exception
}
}
}
}
}

37
src/Squidex.Infrastructure/Tasks/AsyncLockPool.cs

@ -0,0 +1,37 @@
// ==========================================================================
// AsyncLockPool.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.Tasks
{
public sealed class AsyncLockPool
{
private readonly AsyncLock[] locks;
public AsyncLockPool(int poolSize)
{
Guard.GreaterThan(poolSize, 0, nameof(poolSize));
locks = new AsyncLock[poolSize];
for (var i = 0; i < poolSize; i++)
{
locks[i] = new AsyncLock();
}
}
public Task<IDisposable> LockAsync(object target)
{
Guard.NotNull(target, nameof(target));
return locks[Math.Abs(target.GetHashCode() % locks.Length)].LockAsync();
}
}
}
Loading…
Cancel
Save