Browse Source

Feature grains.

pull/283/head
Sebastian 8 years ago
parent
commit
274a371b27
  1. 2
      Dockerfile
  2. 44
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs
  3. 64
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  4. 44
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository_SnapshotStore.cs
  5. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  6. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  7. 15
      src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs
  8. 41
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs
  9. 45
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs
  10. 42
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs
  11. 45
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs
  12. 54
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs
  13. 43
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs
  14. 31
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  15. 38
      src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs
  16. 47
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexCommandMiddleware.cs
  17. 80
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs
  18. 80
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs
  19. 73
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexGrain.cs
  20. 27
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsByNameIndex.cs
  21. 15
      src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsByUserIndex.cs
  22. 5
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  23. 4
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  24. 3
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  25. 26
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentStateScheduleItem.cs
  26. 15
      src/Squidex.Domain.Apps.Entities/Rules/Indexes/IRulesByAppIndex.cs
  27. 56
      src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesByAppIndexCommandMiddleware.cs
  28. 73
      src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesByAppIndexGrain.cs
  29. 5
      src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs
  30. 27
      src/Squidex.Domain.Apps.Entities/Schemas/Indexes/ISchemasByAppIndex.cs
  31. 56
      src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasByAppIndexCommandMiddleware.cs
  32. 80
      src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasByAppIndexGrain.cs
  33. 5
      src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  34. 13
      src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs
  35. 1
      src/Squidex.Infrastructure.MongoDb/States/MongoState.cs
  36. 3
      src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs
  37. 2
      src/Squidex.Infrastructure/Orleans/Bootstrap.cs
  38. 12
      src/Squidex.Infrastructure/Orleans/SingleGrain.cs
  39. 18
      src/Squidex.Infrastructure/States/CollectionNameAttribute.cs
  40. 2
      src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs
  41. 3
      src/Squidex.Infrastructure/States/ISnapshotStore.cs
  42. 3
      src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
  43. 34
      src/Squidex/Config/Domain/EntitiesServices.cs
  44. 71
      src/Squidex/Config/Domain/StoreServices.cs
  45. 3
      tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/OrleansEventNotifierTests.cs
  46. 15
      tools/Migrate_01/MigrationPath.cs
  47. 8
      tools/Migrate_01/Migrations/AddPatterns.cs
  48. 44
      tools/Migrate_01/Migrations/ConvertOldSnapshotStores.cs
  49. 127
      tools/Migrate_01/Migrations/PopulateGrainIndexes.cs
  50. 16
      tools/Migrate_01/Rebuilder.cs

2
Dockerfile

@ -38,7 +38,7 @@ FROM microsoft/aspnetcore:2.0.3-jessie
# Default AspNetCore directory # Default AspNetCore directory
WORKDIR /app WORKDIR /app
# Copy from nuild stage # Copy from build stage
COPY --from=builder /out/ . COPY --from=builder /out/ .
EXPOSE 80 EXPOSE 80

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

@ -1,44 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed class MongoAppEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonElement]
[BsonRequired]
[BsonJson]
public AppState State { get; set; }
[BsonElement]
[BsonRequired]
public long Version { get; set; }
[BsonElement]
[BsonRequired]
public string Name { get; set; }
[BsonElement]
[BsonRequired]
public string[] UserIds { get; set; }
[BsonElement]
[BsonIgnoreIfDefault]
public bool IsArchived { get; set; }
}
}

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

@ -1,64 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed partial class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Apps";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.UserIds));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
}
public async Task<IReadOnlyList<Guid>> QueryAppIdsAsync()
{
var appEntities =
await Collection.Find(new BsonDocument()).Only(x => x.Id)
.ToListAsync();
return appEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<IReadOnlyList<Guid>> QueryUserAppIdsAsync(string userId)
{
var appEntities =
await Collection.Find(x => x.UserIds.Contains(userId) && x.IsArchived != true).Only(x => x.Id)
.ToListAsync();
return appEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<Guid> FindAppIdByNameAsync(string name)
{
var appEntity =
await Collection.Find(x => x.Name == name && x.IsArchived != true).Only(x => x.Id)
.FirstOrDefaultAsync();
return appEntity != null ? Guid.Parse(appEntity["_id"].AsString) : Guid.Empty;
}
}
}

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

@ -1,44 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
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.MongoDb;
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 Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion)
{
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.Name, value.Name)
.Set(x => x.State, value)
.Set(x => x.UserIds, value.Contributors.Keys.ToArray())
.Set(x => x.IsArchived, value.IsArchived));
}
}
}

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

@ -17,6 +17,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public sealed partial class MongoAssetRepository : ISnapshotStore<AssetState, Guid> public sealed partial class MongoAssetRepository : ISnapshotStore<AssetState, Guid>
{ {
Task ISnapshotStore<AssetState, Guid>.ReadAllAsync(Func<AssetState, long, Task> callback)
{
throw new NotSupportedException();
}
public async Task<(AssetState Value, long Version)> ReadAsync(Guid key) public async Task<(AssetState Value, long Version)> ReadAsync(Guid key)
{ {
var existing = var existing =

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

@ -20,6 +20,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public partial class MongoContentRepository : ISnapshotStore<ContentState, Guid> public partial class MongoContentRepository : ISnapshotStore<ContentState, Guid>
{ {
Task ISnapshotStore<ContentState, Guid>.ReadAllAsync(Func<ContentState, long, Task> callback)
{
throw new NotSupportedException();
}
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key) public async Task<(ContentState Value, long Version)> ReadAsync(Guid key)
{ {
var contentEntity = var contentEntity =

15
src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs

@ -11,8 +11,6 @@ using NodaTime;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Infrastructure; 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.Entities.MongoDb.History namespace Squidex.Domain.Apps.Entities.MongoDb.History
{ {
internal sealed class ParsedHistoryEvent : IHistoryEventEntity internal sealed class ParsedHistoryEvent : IHistoryEventEntity
@ -23,19 +21,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History
public Guid Id public Guid Id
{ {
get { return inner.Id; } get { return inner.Id; }
set { } }
public Guid EventId
{
get { return inner.Id; }
} }
public Instant Created public Instant Created
{ {
get { return inner.Created; } get { return inner.Created; }
set { }
} }
public Instant LastModified public Instant LastModified
{ {
get { return inner.LastModified; } get { return inner.LastModified; }
set { }
} }
public RefToken Actor public RefToken Actor
@ -43,11 +43,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History
get { return inner.Actor; } get { return inner.Actor; }
} }
public Guid EventId
{
get { return inner.Id; }
}
public long Version public long Version
{ {
get { return inner.Version; } get { return inner.Version; }

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

@ -1,41 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed class MongoRuleEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonElement]
[BsonRequired]
[BsonRepresentation(BsonType.String)]
public Guid AppId { get; set; }
[BsonElement]
[BsonRequired]
[BsonJson]
public RuleState State { get; set; }
[BsonElement]
[BsonRequired]
public long Version { get; set; }
[BsonElement]
[BsonRequired]
public bool IsDeleted { get; set; }
}
}

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

@ -1,45 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed partial class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository
{
public MongoRuleRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Rules";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection)
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsDeleted));
}
public async Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId)
{
var ruleEntities =
await Collection.Find(x => x.AppId == appId && !x.IsDeleted).Only(x => x.Id)
.ToListAsync();
return ruleEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
}
}

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

@ -1,42 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
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 Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion)
{
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId.Id)
.Set(x => x.IsDeleted, value.IsDeleted));
}
}
}

45
src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs

@ -1,45 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{
public sealed class MongoSchemaEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonElement]
[BsonRequired]
[BsonRepresentation(BsonType.String)]
public Guid AppId { get; set; }
[BsonElement]
[BsonRequired]
[BsonJson]
public SchemaState State { get; set; }
[BsonElement]
[BsonRequired]
public string Name { get; set; }
[BsonElement]
[BsonRequired]
public long Version { get; set; }
[BsonElement]
[BsonRequired]
public bool IsDeleted { get; set; }
}
}

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

@ -1,54 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{
public sealed partial class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository
{
public MongoSchemaRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Schemas";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection)
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.IsDeleted));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.Name).Ascending(x => x.IsDeleted));
}
public async Task<Guid> FindSchemaIdAsync(Guid appId, string name)
{
var schemaEntity =
await Collection.Find(x => x.AppId == appId && x.Name == name && !x.IsDeleted).Only(x => x.Id).SortByDescending(x => x.Version)
.FirstOrDefaultAsync();
return schemaEntity != null ? Guid.Parse(schemaEntity["_id"].AsString) : Guid.Empty;
}
public async Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId)
{
var schemaEntities =
await Collection.Find(x => x.AppId == appId && !x.IsDeleted).Only(x => x.Id)
.ToListAsync();
return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
}
}

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

@ -1,43 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
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 Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion)
{
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId.Id)
.Set(x => x.Name, value.Name)
.Set(x => x.IsDeleted, value.IsDeleted));
}
}
}

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

@ -11,11 +11,8 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
@ -26,29 +23,15 @@ namespace Squidex.Domain.Apps.Entities
public sealed class AppProvider : IAppProvider public sealed class AppProvider : IAppProvider
{ {
private readonly IGrainFactory grainFactory; private readonly IGrainFactory grainFactory;
private readonly IAppRepository appRepository;
private readonly IRuleRepository ruleRepository;
private readonly IRequestCache requestCache; private readonly IRequestCache requestCache;
private readonly ISchemaRepository schemaRepository;
public AppProvider(IGrainFactory grainFactory, IRequestCache requestCache)
public AppProvider(
IGrainFactory grainFactory,
IAppRepository appRepository,
ISchemaRepository schemaRepository,
IRuleRepository ruleRepository,
IRequestCache requestCache)
{ {
Guard.NotNull(grainFactory, nameof(grainFactory)); Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(appRepository, nameof(appRepository));
Guard.NotNull(schemaRepository, nameof(schemaRepository));
Guard.NotNull(requestCache, nameof(requestCache)); Guard.NotNull(requestCache, nameof(requestCache));
Guard.NotNull(ruleRepository, nameof(ruleRepository));
this.grainFactory = grainFactory; this.grainFactory = grainFactory;
this.appRepository = appRepository;
this.schemaRepository = schemaRepository;
this.requestCache = requestCache; this.requestCache = requestCache;
this.ruleRepository = ruleRepository;
} }
public Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id) public Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id)
@ -143,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities
{ {
using (Profile.Method<AppProvider>()) using (Profile.Method<AppProvider>())
{ {
var ids = await schemaRepository.QuerySchemaIdsAsync(appId); var ids = await grainFactory.GetGrain<ISchemasByAppIndex>(appId).GetSchemaIdsAsync();
var schemas = var schemas =
await Task.WhenAll( await Task.WhenAll(
@ -160,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities
{ {
using (Profile.Method<AppProvider>()) using (Profile.Method<AppProvider>())
{ {
var ids = await ruleRepository.QueryRuleIdsAsync(appId); var ids = await grainFactory.GetGrain<IRulesByAppIndex>(appId).GetRuleIdsAsync();
var rules = var rules =
await Task.WhenAll( await Task.WhenAll(
@ -177,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities
{ {
using (Profile.Method<AppProvider>()) using (Profile.Method<AppProvider>())
{ {
var ids = await appRepository.QueryUserAppIdsAsync(userId); var ids = await grainFactory.GetGrain<IAppsByUserIndex>(userId).GetAppIdsAsync();
var apps = var apps =
await Task.WhenAll( await Task.WhenAll(
@ -192,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities
{ {
using (Profile.Method<AppProvider>()) using (Profile.Method<AppProvider>())
{ {
return await appRepository.FindAppIdByNameAsync(name); return await grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).GetAppIdAsync(name);
} }
} }
@ -200,7 +183,7 @@ namespace Squidex.Domain.Apps.Entities
{ {
using (Profile.Method<AppProvider>()) using (Profile.Method<AppProvider>())
{ {
return await schemaRepository.FindSchemaIdAsync(appId, name); return await grainFactory.GetGrain<ISchemasByAppIndex>(appId).GetSchemaIdAsync(name);
} }
} }

38
src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage<AppContributorAssigned>( AddEventMessage<AppContributorAssigned>(
"assigned {user:[Contributor]} as [Permission]"); "assigned {user:[Contributor]} as {[Permission]}");
AddEventMessage<AppContributorRemoved>( AddEventMessage<AppContributorRemoved>(
"removed {user:[Contributor]} from app"); "removed {user:[Contributor]} from app");
@ -48,6 +48,15 @@ namespace Squidex.Domain.Apps.Entities.Apps
AddEventMessage<AppMasterLanguageSet>( AddEventMessage<AppMasterLanguageSet>(
"changed master language to {[Language]}"); "changed master language to {[Language]}");
AddEventMessage<AppPatternAdded>(
"added pattern {[Name]}");
AddEventMessage<AppPatternDeleted>(
"deleted pattern {[Name]}");
AddEventMessage<AppPatternUpdated>(
"updated pattern {[Name]}");
} }
protected Task<HistoryEventToStore> On(AppContributorRemoved @event) protected Task<HistoryEventToStore> On(AppContributorRemoved @event)
@ -131,6 +140,33 @@ namespace Squidex.Domain.Apps.Entities.Apps
.AddParameter("Language", @event.Language)); .AddParameter("Language", @event.Language));
} }
protected Task<HistoryEventToStore> On(AppPatternAdded @event)
{
const string channel = "settings.patterns";
return Task.FromResult(
ForEvent(@event, channel)
.AddParameter("Name", @event.Name));
}
protected Task<HistoryEventToStore> On(AppPatternUpdated @event)
{
const string channel = "settings.patterns";
return Task.FromResult(
ForEvent(@event, channel)
.AddParameter("Name", @event.Name));
}
protected Task<HistoryEventToStore> On(AppPatternDeleted @event)
{
const string channel = "settings.patterns";
return Task.FromResult(
ForEvent(@event, channel)
.AddParameter("Name", @event.Name));
}
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event) protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event)
{ {
return this.DispatchFuncAsync(@event.Payload, (HistoryEventToStore)null); return this.DispatchFuncAsync(@event.Payload, (HistoryEventToStore)null);

47
src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexCommandMiddleware.cs

@ -0,0 +1,47 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{
public sealed class AppsByNameIndexCommandMiddleware : ICommandMiddleware
{
private readonly IAppsByNameIndex index;
public AppsByNameIndexCommandMiddleware(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
}
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.IsCompleted)
{
switch (context.Command)
{
case CreateApp createApp:
await index.AddAppAsync(createApp.AppId, createApp.Name);
break;
case ArchiveApp archiveApp:
await index.RemoveAppAsync(archiveApp.AppId);
break;
}
}
await next();
}
}
}

80
src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs

@ -0,0 +1,80 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{
public sealed class AppsByNameIndexGrain : GrainOfString, IAppsByNameIndex
{
private readonly IStore<string> store;
private IPersistence<State> persistence;
private State state = new State();
[CollectionName("Index_AppsByName")]
private sealed class State
{
public Dictionary<string, Guid> Apps { get; set; } = new Dictionary<string, Guid>();
}
public AppsByNameIndexGrain(IStore<string> store)
{
Guard.NotNull(store, nameof(store));
this.store = store;
}
public override Task OnActivateAsync(string key)
{
persistence = store.WithSnapshots<AppsByNameIndexGrain, State, string>(key, s =>
{
state = s;
});
return persistence.ReadAsync();
}
public Task RebuildAsync(Dictionary<string, Guid> apps)
{
state = new State { Apps = apps };
return persistence.WriteSnapshotAsync(state);
}
public Task AddAppAsync(Guid appId, string name)
{
state.Apps[name] = appId;
return persistence.WriteSnapshotAsync(state);
}
public Task RemoveAppAsync(Guid appId)
{
state.Apps.Remove(state.Apps.FirstOrDefault(x => x.Value == appId).Key ?? string.Empty);
return persistence.WriteSnapshotAsync(state);
}
public Task<Guid> GetAppIdAsync(string appName)
{
state.Apps.TryGetValue(appName, out var appId);
return Task.FromResult(appId);
}
public Task<List<Guid>> GetAppIdAsync()
{
return Task.FromResult(state.Apps.Values.ToList());
}
}
}

80
src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs

@ -0,0 +1,80 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{
public sealed class AppsByUserIndexCommandMiddleware : ICommandMiddleware
{
private readonly IGrainFactory grainFactory;
public AppsByUserIndexCommandMiddleware(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.IsCompleted)
{
switch (context.Command)
{
case CreateApp createApp:
await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId);
break;
case AssignContributor assignContributor:
await Index(GetUserId(context)).AddAppAsync(assignContributor.AppId);
break;
case RemoveContributor removeContributor:
await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId);
break;
case ArchiveApp archiveApp:
{
var appState = await grainFactory.GetGrain<IAppGrain>(archiveApp.AppId).GetStateAsync();
foreach (var contributorId in appState.Value.Contributors.Keys)
{
await Index(contributorId).RemoveAppAsync(archiveApp.AppId);
}
break;
}
}
}
await next();
}
private static string GetUserId(RemoveContributor removeContributor)
{
return removeContributor.ContributorId;
}
private static string GetUserId(CreateApp createApp)
{
return createApp.Actor.Identifier;
}
private static string GetUserId(CommandContext context)
{
return context.Result<EntityCreatedResult<string>>().IdOrValue;
}
private IAppsByUserIndex Index(string id)
{
return grainFactory.GetGrain<IAppsByUserIndex>(id);
}
}
}

73
src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexGrain.cs

@ -0,0 +1,73 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{
public sealed class AppsByUserIndexGrain : GrainOfString, IAppsByUserIndex
{
private readonly IStore<string> store;
private IPersistence<State> persistence;
private State state = new State();
[CollectionName("Index_AppsByUser")]
private sealed class State
{
public HashSet<Guid> Apps { get; set; } = new HashSet<Guid>();
}
public AppsByUserIndexGrain(IStore<string> store)
{
Guard.NotNull(store, nameof(store));
this.store = store;
}
public override Task OnActivateAsync(string key)
{
persistence = store.WithSnapshots<AppsByUserIndexGrain, State, string>(key, s =>
{
state = s;
});
return persistence.ReadAsync();
}
public Task RebuildAsync(HashSet<Guid> apps)
{
state = new State { Apps = apps };
return persistence.WriteSnapshotAsync(state);
}
public Task AddAppAsync(Guid appId)
{
state.Apps.Add(appId);
return persistence.WriteSnapshotAsync(state);
}
public Task RemoveAppAsync(Guid appId)
{
state.Apps.Remove(appId);
return persistence.WriteSnapshotAsync(state);
}
public Task<List<Guid>> GetAppIdsAsync()
{
return Task.FromResult(state.Apps.ToList());
}
}
}

27
src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsByNameIndex.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
namespace Squidex.Domain.Apps.Entities.Apps
{
public interface IAppsByNameIndex : IGrainWithStringKey
{
Task AddAppAsync(Guid appId, string name);
Task RemoveAppAsync(Guid appId);
Task RebuildAsync(Dictionary<string, Guid> apps);
Task<Guid> GetAppIdAsync(string name);
Task<List<Guid>> GetAppIdAsync();
}
}

15
src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs → src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsByUserIndex.cs

@ -1,22 +1,25 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.Repositories namespace Squidex.Domain.Apps.Entities.Apps
{ {
public interface IAppRepository public interface IAppsByUserIndex : IGrainWithStringKey
{ {
Task<Guid> FindAppIdByNameAsync(string name); Task AddAppAsync(Guid appId);
Task<IReadOnlyList<Guid>> QueryAppIdsAsync(); Task RemoveAppAsync(Guid appId);
Task<IReadOnlyList<Guid>> QueryUserAppIdsAsync(string userId); Task RebuildAsync(HashSet<Guid> apps);
Task<List<Guid>> GetAppIdsAsync();
} }
} }

5
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -12,11 +12,12 @@ using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Apps.State namespace Squidex.Domain.Apps.Entities.Apps.State
{ {
public class AppState : DomainObjectState<AppState>, [CollectionName("Apps")]
IAppEntity public class AppState : DomainObjectState<AppState>, IAppEntity
{ {
[JsonProperty] [JsonProperty]
public string Name { get; set; } public string Name { get; set; }

4
src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs

@ -17,9 +17,7 @@ using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets.State namespace Squidex.Domain.Apps.Entities.Assets.State
{ {
public class AssetState : DomainObjectState<AssetState>, public class AssetState : DomainObjectState<AssetState>, IAssetEntity, IAssetInfo
IAssetEntity,
IAssetInfo
{ {
[JsonProperty] [JsonProperty]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }

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

@ -17,8 +17,7 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Entities.Contents.State namespace Squidex.Domain.Apps.Entities.Contents.State
{ {
public class ContentState : DomainObjectState<ContentState>, public class ContentState : DomainObjectState<ContentState>, IContentEntity
IContentEntity
{ {
[JsonProperty] [JsonProperty]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }

26
src/Squidex.Domain.Apps.Entities/Contents/State/ContentStateScheduleItem.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.State
{
public sealed class ContentStateScheduleItem : IContentScheduleItem
{
[JsonProperty]
public Instant ScheduledAt { get; set; }
[JsonProperty]
public RefToken ScheduledBy { get; set; }
[JsonProperty]
public Status ScheduledTo { get; set; }
}
}

15
src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs → src/Squidex.Domain.Apps.Entities/Rules/Indexes/IRulesByAppIndex.cs

@ -1,20 +1,25 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas.Repositories namespace Squidex.Domain.Apps.Entities.Rules
{ {
public interface ISchemaRepository public interface IRulesByAppIndex : IGrainWithGuidKey
{ {
Task<Guid> FindSchemaIdAsync(Guid appId, string name); Task AddRuleAsync(Guid ruleId);
Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId); Task RemoveRuleAsync(Guid ruleId);
Task RebuildAsync(HashSet<Guid> rules);
Task<List<Guid>> GetRuleIdsAsync();
} }
} }

56
src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesByAppIndexCommandMiddleware.cs

@ -0,0 +1,56 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Rules.Indexes
{
public sealed class RulesByAppIndexCommandMiddleware : ICommandMiddleware
{
private readonly IGrainFactory grainFactory;
public RulesByAppIndexCommandMiddleware(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.IsCompleted)
{
switch (context.Command)
{
case CreateRule createRule:
await Index(createRule.AppId.Id).AddRuleAsync(createRule.RuleId);
break;
case DeleteRule deleteRule:
{
var schema = await grainFactory.GetGrain<IRuleGrain>(deleteRule.RuleId).GetStateAsync();
await Index(schema.Value.AppId.Id).RemoveRuleAsync(deleteRule.RuleId);
break;
}
}
}
await next();
}
private IRulesByAppIndex Index(Guid appId)
{
return grainFactory.GetGrain<IRulesByAppIndex>(appId);
}
}
}

73
src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesByAppIndexGrain.cs

@ -0,0 +1,73 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Rules.Indexes
{
public sealed class RulesByAppIndexGrain : GrainOfGuid, IRulesByAppIndex
{
private readonly IStore<Guid> store;
private IPersistence<State> persistence;
private State state = new State();
[CollectionName("Index_RulesByApp")]
private sealed class State
{
public HashSet<Guid> Rules { get; set; } = new HashSet<Guid>();
}
public RulesByAppIndexGrain(IStore<Guid> store)
{
Guard.NotNull(store, nameof(store));
this.store = store;
}
public override Task OnActivateAsync(Guid key)
{
persistence = store.WithSnapshots<RulesByAppIndexGrain, State, Guid>(key, s =>
{
state = s;
});
return persistence.ReadAsync();
}
public Task RebuildAsync(HashSet<Guid> rules)
{
state = new State { Rules = rules };
return persistence.WriteSnapshotAsync(state);
}
public Task AddRuleAsync(Guid ruleId)
{
state.Rules.Add(ruleId);
return persistence.WriteSnapshotAsync(state);
}
public Task RemoveRuleAsync(Guid ruleId)
{
state.Rules.Remove(ruleId);
return persistence.WriteSnapshotAsync(state);
}
public Task<List<Guid>> GetRuleIdsAsync()
{
return Task.FromResult(state.Rules.ToList());
}
}
}

5
src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs

@ -13,11 +13,12 @@ using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Rules.State namespace Squidex.Domain.Apps.Entities.Rules.State
{ {
public class RuleState : DomainObjectState<RuleState>, [CollectionName("Rules")]
IRuleEntity public class RuleState : DomainObjectState<RuleState>, IRuleEntity
{ {
[JsonProperty] [JsonProperty]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }

27
src/Squidex.Domain.Apps.Entities/Schemas/Indexes/ISchemasByAppIndex.cs

@ -0,0 +1,27 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
namespace Squidex.Domain.Apps.Entities.Schemas
{
public interface ISchemasByAppIndex : IGrainWithGuidKey
{
Task AddSchemaAsync(Guid schemaId, string name);
Task RemoveSchemaAsync(Guid schemaId);
Task RebuildAsync(Dictionary<string, Guid> schemas);
Task<Guid> GetSchemaIdAsync(string name);
Task<List<Guid>> GetSchemaIdsAsync();
}
}

56
src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasByAppIndexCommandMiddleware.cs

@ -0,0 +1,56 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{
public sealed class SchemasByAppIndexCommandMiddleware : ICommandMiddleware
{
private readonly IGrainFactory grainFactory;
public SchemasByAppIndexCommandMiddleware(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.IsCompleted)
{
switch (context.Command)
{
case CreateSchema createSchema:
await Index(createSchema.AppId.Id).AddSchemaAsync(createSchema.SchemaId, createSchema.Name);
break;
case DeleteSchema deleteSchema:
{
var schema = await grainFactory.GetGrain<ISchemaGrain>(deleteSchema.SchemaId).GetStateAsync();
await Index(schema.Value.AppId.Id).RemoveSchemaAsync(deleteSchema.SchemaId);
break;
}
}
}
await next();
}
private ISchemasByAppIndex Index(Guid appId)
{
return grainFactory.GetGrain<ISchemasByAppIndex>(appId);
}
}
}

80
src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasByAppIndexGrain.cs

@ -0,0 +1,80 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{
public sealed class SchemasByAppIndexGrain : GrainOfGuid, ISchemasByAppIndex
{
private readonly IStore<Guid> store;
private IPersistence<State> persistence;
private State state = new State();
[CollectionName("Index_SchemasByApp")]
private sealed class State
{
public Dictionary<string, Guid> Schemas { get; set; } = new Dictionary<string, Guid>();
}
public SchemasByAppIndexGrain(IStore<Guid> store)
{
Guard.NotNull(store, nameof(store));
this.store = store;
}
public override Task OnActivateAsync(Guid key)
{
persistence = store.WithSnapshots<SchemasByAppIndexGrain, State, Guid>(key, s =>
{
state = s;
});
return persistence.ReadAsync();
}
public Task RebuildAsync(Dictionary<string, Guid> schemas)
{
state = new State { Schemas = schemas };
return persistence.WriteSnapshotAsync(state);
}
public Task AddSchemaAsync(Guid schemaId, string name)
{
state.Schemas[name] = schemaId;
return persistence.WriteSnapshotAsync(state);
}
public Task RemoveSchemaAsync(Guid schemaId)
{
state.Schemas.Remove(state.Schemas.FirstOrDefault(x => x.Value == schemaId).Key ?? string.Empty);
return persistence.WriteSnapshotAsync(state);
}
public Task<Guid> GetSchemaIdAsync(string name)
{
state.Schemas.TryGetValue(name, out var schemaId);
return Task.FromResult(schemaId);
}
public Task<List<Guid>> GetSchemaIdsAsync()
{
return Task.FromResult(state.Schemas.Values.ToList());
}
}
}

5
src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs

@ -15,11 +15,12 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas.State namespace Squidex.Domain.Apps.Entities.Schemas.State
{ {
public class SchemaState : DomainObjectState<SchemaState>, [CollectionName("Schemas")]
ISchemaEntity public class SchemaState : DomainObjectState<SchemaState>, ISchemaEntity
{ {
[JsonProperty] [JsonProperty]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }

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

@ -5,7 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
@ -26,7 +28,11 @@ namespace Squidex.Infrastructure.States
protected override string CollectionName() protected override string CollectionName()
{ {
return $"States_{typeof(T).Name}"; var attribute = typeof(T).GetCustomAttributes(true).OfType<CollectionNameAttribute>().FirstOrDefault();
var name = attribute?.Name ?? typeof(T).Name;
return $"States_{name}";
} }
public async Task<(T Value, long Version)> ReadAsync(TKey key) public async Task<(T Value, long Version)> ReadAsync(TKey key)
@ -47,5 +53,10 @@ namespace Squidex.Infrastructure.States
{ {
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u.Set(x => x.Doc, value)); return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u.Set(x => x.Doc, value));
} }
public Task ReadAllAsync(System.Func<T, long, Task> callback)
{
return Collection.Find(new BsonDocument()).ForEachAsync(x => callback(x.Doc, x.Version));
}
} }
} }

1
src/Squidex.Infrastructure.MongoDb/States/MongoState.cs

@ -11,6 +11,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
[BsonIgnoreExtraElements]
public sealed class MongoState<T, TKey> : IVersionedEntity<TKey> public sealed class MongoState<T, TKey> : IVersionedEntity<TKey>
{ {
[BsonId] [BsonId]

3
src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs

@ -7,6 +7,7 @@
using System; using System;
using Orleans; using Orleans;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.EventSourcing.Grains namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
@ -20,7 +21,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
eventConsumerManagerGrain = new Lazy<IEventConsumerManagerGrain>(() => eventConsumerManagerGrain = new Lazy<IEventConsumerManagerGrain>(() =>
{ {
return factory.GetGrain<IEventConsumerManagerGrain>("Default"); return factory.GetGrain<IEventConsumerManagerGrain>(SingleGrain.Id);
}); });
} }

2
src/Squidex.Infrastructure/Orleans/Bootstrap.cs

@ -30,7 +30,7 @@ namespace Squidex.Infrastructure.Orleans
{ {
try try
{ {
var grain = grainFactory.GetGrain<T>("Default"); var grain = grainFactory.GetGrain<T>(SingleGrain.Id);
await grain.ActivateAsync(); await grain.ActivateAsync();

12
src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs → src/Squidex.Infrastructure/Orleans/SingleGrain.cs

@ -1,18 +1,14 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; namespace Squidex.Infrastructure.Orleans
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Rules.Repositories
{ {
public interface IRuleRepository public static class SingleGrain
{ {
Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId); public const string Id = "Default";
} }
} }

18
src/Squidex.Domain.Apps.Entities/Contents/IContentScheduleItem.cs → src/Squidex.Infrastructure/States/CollectionNameAttribute.cs

@ -5,18 +5,18 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using NodaTime; using System;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Infrastructure.States
{ {
public interface IContentScheduleItem [AttributeUsage(AttributeTargets.Class)]
public sealed class CollectionNameAttribute : Attribute
{ {
Status ScheduledTo { get; } public string Name { get; }
Instant ScheduledAt { get; } public CollectionNameAttribute(string name)
{
RefToken ScheduledBy { get; } Name = name;
}
} }
} }

2
src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs

@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.States
{ {
public sealed class DefaultStreamNameResolver : IStreamNameResolver public sealed class DefaultStreamNameResolver : IStreamNameResolver
{ {
private static readonly string[] Suffixes = { "Grain", "DomainObject" }; private static readonly string[] Suffixes = { "Grain", "DomainObject", "State" };
public string GetStreamName(Type aggregateType, string id) public string GetStreamName(Type aggregateType, string id)
{ {

3
src/Squidex.Infrastructure/States/ISnapshotStore.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
@ -16,5 +17,7 @@ namespace Squidex.Infrastructure.States
Task<(T Value, long Version)> ReadAsync(TKey key); Task<(T Value, long Version)> ReadAsync(TKey key);
Task ClearAsync(); Task ClearAsync();
Task ReadAllAsync(Func<T, long, Task> callback);
} }
} }

3
src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs

@ -13,6 +13,7 @@ using Orleans;
using Squidex.Areas.Api.Controllers.EventConsumers.Models; using Squidex.Areas.Api.Controllers.EventConsumers.Models;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Orleans;
using Squidex.Pipeline; using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers.EventConsumers namespace Squidex.Areas.Api.Controllers.EventConsumers
@ -28,7 +29,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
public EventConsumersController(ICommandBus commandBus, IGrainFactory grainFactory) public EventConsumersController(ICommandBus commandBus, IGrainFactory grainFactory)
: base(commandBus) : base(commandBus)
{ {
eventConsumerManagerGrain = grainFactory.GetGrain<IEventConsumerManagerGrain>("Default"); eventConsumerManagerGrain = grainFactory.GetGrain<IEventConsumerManagerGrain>(SingleGrain.Id);
} }
[HttpGet] [HttpGet]

34
src/Squidex/Config/Domain/EntitiesServices.cs

@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Backup;
@ -28,7 +29,9 @@ using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.Indexes;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
@ -116,25 +119,22 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<CreateProfileCommandMiddleware>() services.AddSingletonAs<CreateProfileCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<JintScriptEngine>() services.AddSingletonAs<AppsByNameIndexCommandMiddleware>()
.As<IScriptEngine>(); .As<ICommandMiddleware>();
services.AddSingleton<Func<IGrainCallContext, string>>(DomainObjectGrainFormatter.Format);
services.AddTransientAs<AppGrain>() services.AddSingletonAs<AppsByUserIndexCommandMiddleware>()
.AsSelf(); .As<ICommandMiddleware>();
services.AddTransientAs<AssetGrain>() services.AddSingletonAs<RulesByAppIndexCommandMiddleware>()
.AsSelf(); .As<ICommandMiddleware>();
services.AddTransientAs<ContentGrain>() services.AddSingletonAs<SchemasByAppIndexCommandMiddleware>()
.AsSelf(); .As<ICommandMiddleware>();
services.AddTransientAs<RuleGrain>() services.AddSingletonAs<JintScriptEngine>()
.AsSelf(); .As<IScriptEngine>();
services.AddTransientAs<SchemaGrain>() services.AddSingleton<Func<IGrainCallContext, string>>(DomainObjectGrainFormatter.Format);
.AsSelf();
services.AddSingleton(c => services.AddSingleton(c =>
{ {
@ -172,9 +172,15 @@ namespace Squidex.Config.Domain
services.AddTransientAs<ConvertEventStoreAppId>() services.AddTransientAs<ConvertEventStoreAppId>()
.As<IMigration>(); .As<IMigration>();
services.AddTransientAs<ConvertOldSnapshotStores>()
.As<IMigration>();
services.AddTransientAs<DeleteArchiveCollection>() services.AddTransientAs<DeleteArchiveCollection>()
.As<IMigration>(); .As<IMigration>();
services.AddTransientAs<PopulateGrainIndexes>()
.As<IMigration>();
services.AddTransientAs<RebuildContents>() services.AddTransientAs<RebuildContents>()
.As<IMigration>(); .As<IMigration>();

71
src/Squidex/Config/Domain/StoreServices.cs

@ -12,33 +12,22 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Backup.State;
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.Contents.State;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Domain.Apps.Entities.History.Repositories;
using Squidex.Domain.Apps.Entities.MongoDb.Apps;
using Squidex.Domain.Apps.Entities.MongoDb.Assets; using Squidex.Domain.Apps.Entities.MongoDb.Assets;
using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents;
using Squidex.Domain.Apps.Entities.MongoDb.History; using Squidex.Domain.Apps.Entities.MongoDb.History;
using Squidex.Domain.Apps.Entities.MongoDb.Rules; using Squidex.Domain.Apps.Entities.MongoDb.Rules;
using Squidex.Domain.Apps.Entities.MongoDb.Schemas;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Domain.Users; using Squidex.Domain.Users;
using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb;
using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Domain.Users.MongoDb.Infrastructure;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking; using Squidex.Infrastructure.UsageTracking;
@ -62,63 +51,55 @@ namespace Squidex.Config.Domain
var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName);
var mongoContentDatabase = mongoClient.GetDatabase(mongoContentDatabaseName); var mongoContentDatabase = mongoClient.GetDatabase(mongoContentDatabaseName);
services.AddSingletonAs(c => new MongoXmlRepository(mongoDatabase)) services.AddSingleton(typeof(ISnapshotStore<,>), typeof(MongoSnapshotStore<,>));
.As<IXmlRepository>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoMigrationStatus(mongoDatabase)) services.AddSingletonAs(mongoDatabase)
.As<IMigrationStatus>() .As<IMongoDatabase>();
.As<IInitializable>();
services.AddSingletonAs(c => new MongoSnapshotStore<BackupState, Guid>(mongoDatabase, c.GetRequiredService<JsonSerializer>())) services.AddSingletonAs<MongoXmlRepository>()
.As<ISnapshotStore<BackupState, Guid>>() .As<IXmlRepository>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoSnapshotStore<EventConsumerState, string>(mongoDatabase, c.GetRequiredService<JsonSerializer>())) services.AddSingletonAs<MongoMigrationStatus>()
.As<ISnapshotStore<EventConsumerState, string>>() .As<IMigrationStatus>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoPersistedGrantStore(mongoDatabase)) services.AddSingletonAs<MongoPersistedGrantStore>()
.As<IPersistedGrantStore>() .As<IPersistedGrantStore>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoUsageStore(mongoDatabase)) services.AddSingletonAs<MongoUsageStore>()
.As<IUsageStore>() .As<IUsageStore>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoRuleEventRepository(mongoDatabase)) services.AddSingletonAs<MongoRuleEventRepository>()
.As<IRuleEventRepository>() .As<IRuleEventRepository>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoUserStore(mongoDatabase)) services.AddSingletonAs<MongoUserStore>()
.As<IUserStore<IUser>>() .As<IUserStore<IUser>>()
.As<IUserFactory>() .As<IUserFactory>()
.As<IUserResolver>() .As<IUserResolver>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoRoleStore(mongoDatabase)) services.AddSingletonAs<MongoRoleStore>()
.As<IRoleStore<IRole>>() .As<IRoleStore<IRole>>()
.As<IRoleFactory>() .As<IRoleFactory>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoAppRepository(mongoDatabase)) services.AddSingletonAs<MongoHistoryEventRepository>()
.As<IAppRepository>() .As<IHistoryEventRepository>()
.As<ISnapshotStore<AppState, Guid>>() .As<IEventConsumer>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoAssetRepository(mongoDatabase))
.As<IAssetRepository>()
.As<ISnapshotStore<AssetState, Guid>>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoRuleRepository(mongoDatabase)) services.AddSingletonAs<MongoAssetStatsRepository>()
.As<IRuleRepository>() .As<IAssetStatsRepository>()
.As<ISnapshotStore<RuleState, Guid>>() .As<IEventConsumer>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoSchemaRepository(mongoDatabase)) services.AddSingletonAs<MongoAssetRepository>()
.As<ISchemaRepository>() .As<IAssetRepository>()
.As<ISnapshotStore<SchemaState, Guid>>() .As<ISnapshotStore<AssetState, Guid>>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetService<IAppProvider>())) services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetService<IAppProvider>()))
@ -126,16 +107,6 @@ namespace Squidex.Config.Domain
.As<ISnapshotStore<ContentState, Guid>>() .As<ISnapshotStore<ContentState, Guid>>()
.As<IEventConsumer>() .As<IEventConsumer>()
.As<IInitializable>(); .As<IInitializable>();
services.AddSingletonAs(c => new MongoHistoryEventRepository(mongoDatabase, c.GetServices<IHistoryEventsCreator>()))
.As<IHistoryEventRepository>()
.As<IEventConsumer>()
.As<IInitializable>();
services.AddSingletonAs(c => new MongoAssetStatsRepository(mongoDatabase))
.As<IAssetStatsRepository>()
.As<IEventConsumer>()
.As<IInitializable>();
} }
}); });

3
tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/OrleansEventNotifierTests.cs

@ -7,6 +7,7 @@
using FakeItEasy; using FakeItEasy;
using Orleans; using Orleans;
using Squidex.Infrastructure.Orleans;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.EventSourcing.Grains namespace Squidex.Infrastructure.EventSourcing.Grains
@ -20,7 +21,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
var factory = A.Fake<IGrainFactory>(); var factory = A.Fake<IGrainFactory>();
A.CallTo(() => factory.GetGrain<IEventConsumerManagerGrain>("Default", null)) A.CallTo(() => factory.GetGrain<IEventConsumerManagerGrain>(SingleGrain.Id, null))
.Returns(manager); .Returns(manager);
sut = new OrleansEventNotifier(factory); sut = new OrleansEventNotifier(factory);

15
tools/Migrate_01/MigrationPath.cs

@ -15,7 +15,7 @@ namespace Migrate_01
{ {
public sealed class MigrationPath : IMigrationPath public sealed class MigrationPath : IMigrationPath
{ {
private const int CurrentVersion = 8; private const int CurrentVersion = 9;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public MigrationPath(IServiceProvider serviceProvider) public MigrationPath(IServiceProvider serviceProvider)
@ -50,13 +50,24 @@ namespace Migrate_01
migrations.Add(serviceProvider.GetRequiredService<RebuildSnapshots>()); migrations.Add(serviceProvider.GetRequiredService<RebuildSnapshots>());
} }
// Version 9: Grain Indexes
if (version < 9)
{
migrations.Add(serviceProvider.GetRequiredService<ConvertOldSnapshotStores>());
migrations.Add(serviceProvider.GetRequiredService<PopulateGrainIndexes>());
}
// Version 1: Introduce App patterns. // Version 1: Introduce App patterns.
if (version <= 1) if (version <= 1)
{ {
migrations.Add(serviceProvider.GetRequiredService<AddPatterns>()); migrations.Add(serviceProvider.GetRequiredService<AddPatterns>());
} }
migrations.Add(serviceProvider.GetRequiredService<DeleteArchiveCollection>()); // Version 8: Introduce Archive collection.
if (version < 8)
{
migrations.Add(serviceProvider.GetRequiredService<DeleteArchiveCollection>());
}
return (CurrentVersion, migrations); return (CurrentVersion, migrations);
} }

8
tools/Migrate_01/Migrations/AddPatterns.cs

@ -10,8 +10,8 @@ using System.Threading.Tasks;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Orleans;
namespace Migrate_01.Migrations namespace Migrate_01.Migrations
{ {
@ -19,18 +19,16 @@ namespace Migrate_01.Migrations
{ {
private readonly InitialPatterns initialPatterns; private readonly InitialPatterns initialPatterns;
private readonly IGrainFactory grainFactory; private readonly IGrainFactory grainFactory;
private readonly IAppRepository appRepository;
public AddPatterns(InitialPatterns initialPatterns, IAppRepository appRepository, IGrainFactory grainFactory) public AddPatterns(InitialPatterns initialPatterns, IGrainFactory grainFactory)
{ {
this.initialPatterns = initialPatterns; this.initialPatterns = initialPatterns;
this.appRepository = appRepository;
this.grainFactory = grainFactory; this.grainFactory = grainFactory;
} }
public async Task UpdateAsync() public async Task UpdateAsync()
{ {
var ids = await appRepository.QueryAppIdsAsync(); var ids = await grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).GetAppIdAsync();
foreach (var id in ids) foreach (var id in ids)
{ {

44
tools/Migrate_01/Migrations/ConvertOldSnapshotStores.cs

@ -0,0 +1,44 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure.Migrations;
namespace Migrate_01.Migrations
{
public sealed class ConvertOldSnapshotStores : IMigration
{
private readonly IMongoDatabase database;
public ConvertOldSnapshotStores(IMongoDatabase database)
{
this.database = database;
}
public Task UpdateAsync()
{
var collections = new[]
{
"States_Apps",
"States_Rules",
"States_Schemas"
};
var update = Builders<BsonDocument>.Update.Rename("State", "Doc");
var filter = new BsonDocument();
return Task.WhenAll(
collections
.Select(x => database.GetCollection<BsonDocument>(x))
.Select(x => x.UpdateManyAsync(filter, update)));
}
}
}

127
tools/Migrate_01/Migrations/PopulateGrainIndexes.cs

@ -0,0 +1,127 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Migrate_01.Migrations
{
public class PopulateGrainIndexes : IMigration
{
private readonly IGrainFactory grainFactory;
private readonly ISnapshotStore<AppState, Guid> statesForApps;
private readonly ISnapshotStore<RuleState, Guid> statesForRules;
private readonly ISnapshotStore<SchemaState, Guid> statesForSchemas;
public PopulateGrainIndexes(
IGrainFactory grainFactory,
ISnapshotStore<AppState, Guid> statesForApps,
ISnapshotStore<RuleState, Guid> statesForRules,
ISnapshotStore<SchemaState, Guid> statesForSchemas)
{
this.grainFactory = grainFactory;
this.statesForApps = statesForApps;
this.statesForRules = statesForRules;
this.statesForSchemas = statesForSchemas;
}
public Task UpdateAsync()
{
return Task.WhenAll(
RebuildAppIndexes(),
RebuildRuleIndexes(),
RebuildSchemaIndexes());
}
private async Task RebuildAppIndexes()
{
var appsByName = new Dictionary<string, Guid>();
var appsByUser = new Dictionary<string, HashSet<Guid>>();
await statesForApps.ReadAllAsync((app, version) =>
{
if (!app.IsArchived)
{
appsByName[app.Name] = app.Id;
foreach (var contributor in app.Contributors.Keys)
{
appsByUser.GetOrAddNew(contributor).Add(app.Id);
}
}
return TaskHelper.Done;
});
var tasks =
appsByUser.Select(x =>
grainFactory.GetGrain<IAppsByUserIndex>(x.Key).RebuildAsync(x.Value))
.Union(new[]
{
grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id).RebuildAsync(appsByName)
});
await Task.WhenAll(tasks);
}
private async Task RebuildRuleIndexes()
{
var schemasByApp = new Dictionary<Guid, HashSet<Guid>>();
await statesForRules.ReadAllAsync((schema, version) =>
{
if (!schema.IsDeleted)
{
schemasByApp.GetOrAddNew(schema.AppId.Id).Add(schema.Id);
}
return TaskHelper.Done;
});
var tasks =
schemasByApp.Select(x =>
grainFactory.GetGrain<IRulesByAppIndex>(x.Key).RebuildAsync(x.Value));
await Task.WhenAll(tasks);
}
private async Task RebuildSchemaIndexes()
{
var schemasByApp = new Dictionary<Guid, Dictionary<string, Guid>>();
await statesForSchemas.ReadAllAsync((schema, version) =>
{
if (!schema.IsDeleted)
{
schemasByApp.GetOrAddNew(schema.AppId.Id).Add(schema.Name, schema.Id);
}
return TaskHelper.Done;
});
var tasks =
schemasByApp.Select(x =>
grainFactory.GetGrain<ISchemasByAppIndex>(x.Key).RebuildAsync(x.Value));
await Task.WhenAll(tasks);
}
}
}

16
tools/Migrate_01/Rebuilder.cs

@ -15,6 +15,7 @@ using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.State; using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.State; using Squidex.Domain.Apps.Entities.Rules.State;
@ -138,22 +139,13 @@ namespace Migrate_01
{ {
var @event = ParseKnownEvent(storedEvent); var @event = ParseKnownEvent(storedEvent);
if (@event.Payload is ContentEvent contentEvent) if (@event.Payload is ContentEvent contentEvent && handledIds.Add(contentEvent.ContentId))
{ {
try try
{ {
var (content, version) = await snapshotContentStore.ReadAsync(contentEvent.ContentId); var content = grainFactory.GetGrain<IContentGrain>(contentEvent.ContentId);
if (content == null) await content.WriteSnapshotAsync();
{
version = EtagVersion.Empty;
content = new ContentState();
}
content = content.Apply(@event);
await snapshotContentStore.WriteAsync(contentEvent.ContentId, content, version, version + 1);
} }
catch (DomainObjectNotFoundException) catch (DomainObjectNotFoundException)
{ {

Loading…
Cancel
Save