mirror of https://github.com/Squidex/squidex.git
42 changed files with 774 additions and 581 deletions
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IRuleDequeuerGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Orleans.Concurrency; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Rules.Orleans.Grains |
|||
{ |
|||
public interface IRuleDequeuerGrain : IGrainWithStringKey |
|||
{ |
|||
Task ActivateAsync(); |
|||
|
|||
Task HandleAsync(Immutable<IRuleEventEntity> @event); |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RuleDequeuerBootstrap.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Orleans.Providers; |
|||
using Squidex.Domain.Apps.Read.Rules.Orleans.Grains; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Rules.Orleans |
|||
{ |
|||
public sealed class RuleDequeuerBootstrap : IBootstrapProvider |
|||
{ |
|||
public string Name { get; private set; } |
|||
|
|||
public Task Init(string name, IProviderRuntime providerRuntime, IProviderConfiguration config) |
|||
{ |
|||
var grain = providerRuntime.GrainFactory.GetGrain<IRuleDequeuerGrain>("Default"); |
|||
|
|||
return grain.ActivateAsync(); |
|||
} |
|||
|
|||
public Task Close() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
// ==========================================================================
|
|||
// AppProvider.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 Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.State.Orleans.Grains; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans |
|||
{ |
|||
public sealed class AppProvider : IAppProvider |
|||
{ |
|||
private readonly IStateFactory factory; |
|||
|
|||
public AppProvider(IStateFactory factory) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
|
|||
this.factory = factory; |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(string appName) |
|||
{ |
|||
var app = await factory.GetAsync<AppStateGrain, AppStateGrainState>(appName); |
|||
|
|||
return await app.GetAppAsync(); |
|||
} |
|||
|
|||
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id) |
|||
{ |
|||
var app = await factory.GetAsync<AppStateGrain, AppStateGrainState>(appName); |
|||
|
|||
return await app.GetAppWithSchemaAsync(id); |
|||
} |
|||
|
|||
public async Task<List<IRuleEntity>> GetRulesAsync(string appName) |
|||
{ |
|||
var app = await factory.GetAsync<AppStateGrain, AppStateGrainState>(appName); |
|||
|
|||
return await app.GetRulesAsync(); |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false) |
|||
{ |
|||
var app = await factory.GetAsync<AppStateGrain, AppStateGrainState>(appName); |
|||
|
|||
return await app.GetSchemaAsync(id, provideDeleted); |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false) |
|||
{ |
|||
var app = await factory.GetAsync<AppStateGrain, AppStateGrainState>(appName); |
|||
|
|||
return await app.GetSchemaAsync(name, provideDeleted); |
|||
} |
|||
|
|||
public async Task<List<ISchemaEntity>> GetSchemasAsync(string appName) |
|||
{ |
|||
var app = await factory.GetAsync<AppStateGrain, AppStateGrainState>(appName); |
|||
|
|||
return await app.GetSchemasAsync(); |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetUserApps(string userId) |
|||
{ |
|||
var appUser = await factory.GetAsync<AppUserGrain, AppUserGrainState>(userId); |
|||
var appNames = await appUser.GetAppNamesAsync(); |
|||
|
|||
var tasks = |
|||
appNames |
|||
.Select(x => GetAppAsync(x)); |
|||
|
|||
var apps = await Task.WhenAll(tasks); |
|||
|
|||
return apps.Where(a => a != null).ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrain.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.Core.Schemas; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains |
|||
{ |
|||
public class AppStateGrain : StatefulObject<AppStateGrainState> |
|||
{ |
|||
private readonly FieldRegistry fieldRegistry; |
|||
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(); |
|||
private Exception exception; |
|||
|
|||
public AppStateGrain(FieldRegistry fieldRegistry) |
|||
{ |
|||
Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); |
|||
|
|||
this.fieldRegistry = fieldRegistry; |
|||
} |
|||
|
|||
public override async Task ReadStateAsync() |
|||
{ |
|||
try |
|||
{ |
|||
await base.ReadStateAsync(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
exception = ex; |
|||
|
|||
State = new AppStateGrainState(); |
|||
} |
|||
|
|||
State.SetRegistry(fieldRegistry); |
|||
} |
|||
|
|||
public virtual Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid id) |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted); |
|||
|
|||
return Task.FromResult((State.GetApp(), schema)); |
|||
}); |
|||
} |
|||
|
|||
public virtual Task<IAppEntity> GetAppAsync() |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
var value = State.GetApp(); |
|||
|
|||
return Task.FromResult(value); |
|||
}); |
|||
} |
|||
|
|||
public virtual Task<List<IRuleEntity>> GetRulesAsync() |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
var value = State.FindRules(); |
|||
|
|||
return Task.FromResult(value); |
|||
}); |
|||
} |
|||
|
|||
public virtual Task<List<ISchemaEntity>> GetSchemasAsync() |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
var value = State.FindSchemas(x => !x.IsDeleted); |
|||
|
|||
return Task.FromResult(value); |
|||
}); |
|||
} |
|||
|
|||
public virtual Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false) |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
var value = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); |
|||
|
|||
return Task.FromResult(value); |
|||
}); |
|||
} |
|||
|
|||
public virtual Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false) |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
var value = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); |
|||
|
|||
return Task.FromResult(value); |
|||
}); |
|||
} |
|||
|
|||
public virtual Task HandleAsync(Envelope<IEvent> message) |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
if (exception != null) |
|||
{ |
|||
if (message.Payload is AppCreated) |
|||
{ |
|||
exception = null; |
|||
} |
|||
else |
|||
{ |
|||
throw exception; |
|||
} |
|||
} |
|||
|
|||
State.Apply(message); |
|||
|
|||
return WriteStateAsync(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// ==========================================================================
|
|||
// AppUserGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains |
|||
{ |
|||
public sealed class AppUserGrain : StatefulObject<AppUserGrainState> |
|||
{ |
|||
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(); |
|||
|
|||
public Task AddAppAsync(string appName) |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
State.AppNames.Add(appName); |
|||
|
|||
return WriteStateAsync(); |
|||
}); |
|||
} |
|||
|
|||
public Task RemoveAppAsync(string appName) |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
State.AppNames.Remove(appName); |
|||
|
|||
return WriteStateAsync(); |
|||
}); |
|||
} |
|||
|
|||
public Task<List<string>> GetAppNamesAsync() |
|||
{ |
|||
return dispatcher.DispatchAndUnwrapAsync(() => |
|||
{ |
|||
return Task.FromResult(State.AppNames.ToList()); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAppStateGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Json.Orleans; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains |
|||
{ |
|||
public interface IAppStateGrain : IGrainWithStringKey |
|||
{ |
|||
Task<J<(IAppEntity, ISchemaEntity)>> GetAppWithSchemaAsync(Guid id); |
|||
|
|||
Task<J<IAppEntity>> GetAppAsync(); |
|||
|
|||
Task<J<ISchemaEntity>> GetSchemaAsync(Guid id, bool provideDeleted = false); |
|||
|
|||
Task<J<ISchemaEntity>> GetSchemaAsync(string name, bool provideDeleted = false); |
|||
|
|||
Task<J<List<ISchemaEntity>>> GetSchemasAsync(); |
|||
|
|||
Task<J<List<IRuleEntity>>> GetRulesAsync(); |
|||
|
|||
Task HandleAsync(J<Envelope<IEvent>> message); |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAppUserGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains |
|||
{ |
|||
public interface IAppUserGrain : IGrainWithStringKey |
|||
{ |
|||
Task<List<string>> GetSchemaNamesAsync(); |
|||
|
|||
Task AddAppAsync(string appName); |
|||
|
|||
Task RemoveAppAsync(string appName); |
|||
} |
|||
} |
|||
@ -1,121 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans.Runtime; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Json.Orleans; |
|||
using Squidex.Infrastructure.Orleans; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations |
|||
{ |
|||
public sealed class AppStateGrain : GrainV2<AppStateGrainState>, IAppStateGrain |
|||
{ |
|||
private readonly FieldRegistry fieldRegistry; |
|||
private Exception exception; |
|||
|
|||
public AppStateGrain(FieldRegistry fieldRegistry, IGrainRuntime runtime) |
|||
: base(runtime) |
|||
{ |
|||
Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); |
|||
Guard.NotNull(runtime, nameof(runtime)); |
|||
|
|||
this.fieldRegistry = fieldRegistry; |
|||
} |
|||
|
|||
protected override async Task ReadStateAsync() |
|||
{ |
|||
try |
|||
{ |
|||
await base.ReadStateAsync(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
exception = ex; |
|||
|
|||
State = new AppStateGrainState(); |
|||
} |
|||
} |
|||
|
|||
public override Task OnActivateAsync() |
|||
{ |
|||
State.SetRegistry(fieldRegistry); |
|||
|
|||
return base.OnActivateAsync(); |
|||
} |
|||
|
|||
public Task<J<(IAppEntity, ISchemaEntity)>> GetAppWithSchemaAsync(Guid id) |
|||
{ |
|||
var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted); |
|||
|
|||
return Task.FromResult((State.GetApp(), schema).AsJ()); |
|||
} |
|||
|
|||
public Task<J<IAppEntity>> GetAppAsync() |
|||
{ |
|||
var value = State.GetApp(); |
|||
|
|||
return Task.FromResult(value.AsJ()); |
|||
} |
|||
|
|||
public Task<J<List<IRuleEntity>>> GetRulesAsync() |
|||
{ |
|||
var value = State.FindRules(); |
|||
|
|||
return Task.FromResult(value.AsJ()); |
|||
} |
|||
|
|||
public Task<J<List<ISchemaEntity>>> GetSchemasAsync() |
|||
{ |
|||
var value = State.FindSchemas(x => !x.IsDeleted); |
|||
|
|||
return Task.FromResult(value.AsJ()); |
|||
} |
|||
|
|||
public Task<J<ISchemaEntity>> GetSchemaAsync(Guid id, bool provideDeleted = false) |
|||
{ |
|||
var value = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); |
|||
|
|||
return Task.FromResult(value.AsJ()); |
|||
} |
|||
|
|||
public Task<J<ISchemaEntity>> GetSchemaAsync(string name, bool provideDeleted = false) |
|||
{ |
|||
var value = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); |
|||
|
|||
return Task.FromResult(value.AsJ()); |
|||
} |
|||
|
|||
public Task HandleAsync(J<Envelope<IEvent>> message) |
|||
{ |
|||
if (exception != null) |
|||
{ |
|||
if (message.Value.Payload is AppCreated) |
|||
{ |
|||
exception = null; |
|||
} |
|||
else |
|||
{ |
|||
throw exception; |
|||
} |
|||
} |
|||
|
|||
State.Apply(message.Value); |
|||
|
|||
return WriteStateAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,43 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppUserGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans.Runtime; |
|||
using Squidex.Infrastructure.Orleans; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations |
|||
{ |
|||
public sealed class AppUserGrain : GrainV2<AppUserGrainState>, IAppUserGrain |
|||
{ |
|||
public AppUserGrain(IGrainRuntime runtime) |
|||
: base(runtime) |
|||
{ |
|||
} |
|||
|
|||
public Task AddAppAsync(string appName) |
|||
{ |
|||
State.AppNames.Add(appName); |
|||
|
|||
return WriteStateAsync(); |
|||
} |
|||
|
|||
public Task RemoveAppAsync(string appName) |
|||
{ |
|||
State.AppNames.Remove(appName); |
|||
|
|||
return WriteStateAsync(); |
|||
} |
|||
|
|||
public Task<List<string>> GetSchemaNamesAsync() |
|||
{ |
|||
return Task.FromResult(State.AppNames.ToList()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,129 +0,0 @@ |
|||
// ==========================================================================
|
|||
// OrleansAppProvider.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 Orleans; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.State.Orleans.Grains; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Orleans |
|||
{ |
|||
public sealed class OrleansAppProvider : IAppProvider |
|||
{ |
|||
private readonly IGrainFactory factory; |
|||
private readonly ISemanticLog log; |
|||
|
|||
public OrleansAppProvider(IGrainFactory factory, ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
Guard.NotNull(log, nameof(log)); |
|||
|
|||
this.factory = factory; |
|||
|
|||
this.log = log; |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(string appName) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetAppAsync)))) |
|||
{ |
|||
var result = await factory.GetGrain<IAppStateGrain>(appName).GetAppAsync(); |
|||
|
|||
return result.Value; |
|||
} |
|||
} |
|||
|
|||
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetAppWithSchemaAsync)))) |
|||
{ |
|||
var result = await factory.GetGrain<IAppStateGrain>(appName).GetAppWithSchemaAsync(id); |
|||
|
|||
return result.Value; |
|||
} |
|||
} |
|||
|
|||
public async Task<List<IRuleEntity>> GetRulesAsync(string appName) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetRulesAsync)))) |
|||
{ |
|||
var result = await factory.GetGrain<IAppStateGrain>(appName).GetRulesAsync(); |
|||
|
|||
return result.Value; |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetSchemaAsync)))) |
|||
{ |
|||
var result = await factory.GetGrain<IAppStateGrain>(appName).GetSchemaAsync(id, provideDeleted); |
|||
|
|||
return result.Value; |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetSchemaAsync)))) |
|||
{ |
|||
var result = await factory.GetGrain<IAppStateGrain>(appName).GetSchemaAsync(name, provideDeleted); |
|||
|
|||
return result.Value; |
|||
} |
|||
} |
|||
|
|||
public async Task<List<ISchemaEntity>> GetSchemasAsync(string appName) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetSchemasAsync)))) |
|||
{ |
|||
var result = await factory.GetGrain<IAppStateGrain>(appName).GetSchemasAsync(); |
|||
|
|||
return result.Value; |
|||
} |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetUserApps(string userId) |
|||
{ |
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("module", nameof(OrleansAppProvider)) |
|||
.WriteProperty("method", nameof(GetUserApps)))) |
|||
{ |
|||
var schemaIds = await factory.GetGrain<IAppUserGrain>(userId).GetSchemaNamesAsync(); |
|||
|
|||
var tasks = |
|||
schemaIds |
|||
.Select(x => factory.GetGrain<IAppStateGrain>(x)) |
|||
.Select(x => x.GetAppAsync()); |
|||
|
|||
var apps = await Task.WhenAll(tasks); |
|||
|
|||
return apps.Select(a => a.Value).Where(a => a != null).ToList(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
// ==========================================================================
|
|||
// MongoStateStore.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Infrastructure.States |
|||
{ |
|||
public sealed class MongoStateStore : IStateStore, IExternalSystem |
|||
{ |
|||
private const string FieldId = "_id"; |
|||
private const string FieldDoc = "_doc"; |
|||
private const string FieldEtag = "_etag"; |
|||
private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; |
|||
private static readonly FilterDefinitionBuilder<BsonDocument> Filter = Builders<BsonDocument>.Filter; |
|||
private static readonly UpdateDefinitionBuilder<BsonDocument> Update = Builders<BsonDocument>.Update; |
|||
private static readonly ProjectionDefinitionBuilder<BsonDocument> Projection = Builders<BsonDocument>.Projection; |
|||
private readonly IMongoDatabase database; |
|||
private readonly JsonSerializer serializer; |
|||
|
|||
public MongoStateStore(IMongoDatabase database, JsonSerializer serializer) |
|||
{ |
|||
Guard.NotNull(database, nameof(database)); |
|||
Guard.NotNull(serializer, nameof(serializer)); |
|||
|
|||
this.database = database; |
|||
this.serializer = serializer; |
|||
} |
|||
|
|||
public void Connect() |
|||
{ |
|||
try |
|||
{ |
|||
database.ListCollections(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new ConfigurationException($"MongoDb connection failed to connect to database {database.DatabaseNamespace.DatabaseName}", ex); |
|||
} |
|||
} |
|||
|
|||
public async Task<(T Value, string Etag)> ReadAsync<T>(string key) |
|||
{ |
|||
var collection = GetCollection<T>(); |
|||
|
|||
var existing = |
|||
await collection.Find(Filter.Eq(FieldId, key)) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existing != null) |
|||
{ |
|||
var value = existing[FieldDoc].AsBsonDocument.ToJson().ToObject<T>(serializer); |
|||
|
|||
return (value, existing[FieldEtag].AsString); |
|||
} |
|||
|
|||
return (default(T), null); |
|||
} |
|||
|
|||
public async Task WriteAsync<T>(string key, T value, string oldEtag, string newEtag) |
|||
{ |
|||
var collection = GetCollection<T>(); |
|||
|
|||
var newData = JToken.FromObject(value, serializer).ToBson(); |
|||
|
|||
try |
|||
{ |
|||
await collection.UpdateOneAsync( |
|||
Filter.And( |
|||
Filter.Eq(FieldId, key), |
|||
Filter.Eq(FieldEtag, oldEtag) |
|||
), |
|||
Update |
|||
.Set(FieldEtag, newEtag) |
|||
.Set(FieldDoc, newData), |
|||
Upsert); |
|||
} |
|||
catch (MongoWriteException ex) |
|||
{ |
|||
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
var existingEtag = |
|||
await collection.Find(Filter.Eq(FieldId, key)) |
|||
.Project<BsonDocument>(Projection.Exclude(FieldDoc)).FirstOrDefaultAsync(); |
|||
|
|||
if (existingEtag != null && existingEtag.Contains(FieldEtag)) |
|||
{ |
|||
throw new InconsistentStateException(existingEtag[FieldEtag].AsString, oldEtag, ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IMongoCollection<BsonDocument> GetCollection<T>() |
|||
{ |
|||
return database.GetCollection<BsonDocument>($"States_{typeof(T).Name}"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// DefaultEventNotifier.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public sealed class DefaultEventNotifier : IEventNotifier |
|||
{ |
|||
private static readonly string ChannelName = typeof(DefaultEventNotifier).Name; |
|||
|
|||
private readonly IPubSub pubsub; |
|||
|
|||
public sealed class EventNotification |
|||
{ |
|||
public string StreamName { get; set; } |
|||
} |
|||
|
|||
public DefaultEventNotifier(IPubSub pubsub) |
|||
{ |
|||
Guard.NotNull(pubsub, nameof(pubsub)); |
|||
|
|||
this.pubsub = pubsub; |
|||
} |
|||
|
|||
public void NotifyEventsStored(string streamName) |
|||
{ |
|||
pubsub.Publish(new EventNotification { StreamName = streamName }, true); |
|||
} |
|||
|
|||
public IDisposable Subscribe(Action<string> handler) |
|||
{ |
|||
return pubsub.Subscribe<EventNotification>(x => handler?.Invoke(x.StreamName)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// ==========================================================================
|
|||
// InconsistentStateException.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Runtime.Serialization; |
|||
|
|||
namespace Squidex.Infrastructure.States |
|||
{ |
|||
[Serializable] |
|||
public class InconsistentStateException : Exception |
|||
{ |
|||
private readonly string currentEtag; |
|||
private readonly string expectedEtag; |
|||
|
|||
public string CurrentEtag |
|||
{ |
|||
get { return currentEtag; } |
|||
} |
|||
|
|||
public string ExpectedEtag |
|||
{ |
|||
get { return expectedEtag; } |
|||
} |
|||
|
|||
public InconsistentStateException(string currentEtag, string expectedEtag, Exception ex) |
|||
: base(FormatMessage(currentEtag, expectedEtag), ex) |
|||
{ |
|||
this.currentEtag = currentEtag; |
|||
|
|||
this.expectedEtag = expectedEtag; |
|||
} |
|||
|
|||
protected InconsistentStateException(SerializationInfo info, StreamingContext context) |
|||
: base(info, context) |
|||
{ |
|||
} |
|||
|
|||
private static string FormatMessage(string currentEtag, string expectedEtag) |
|||
{ |
|||
return $"Requested etag {expectedEtag}, but found {currentEtag}."; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// ==========================================================================
|
|||
// DefaultEventNotifierTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public sealed class DefaultEventNotifierTests |
|||
{ |
|||
private readonly DefaultEventNotifier sut = new DefaultEventNotifier(new InMemoryPubSub()); |
|||
|
|||
[Fact] |
|||
public void Should_invalidate_all_actions() |
|||
{ |
|||
var handler1Handled = 0; |
|||
var handler2Handled = 0; |
|||
|
|||
var streamNames = new List<string>(); |
|||
|
|||
sut.Subscribe(x => |
|||
{ |
|||
streamNames.Add(x); |
|||
|
|||
handler1Handled++; |
|||
}); |
|||
|
|||
sut.NotifyEventsStored("a"); |
|||
|
|||
sut.Subscribe(x => |
|||
{ |
|||
streamNames.Add(x); |
|||
|
|||
handler2Handled++; |
|||
}); |
|||
|
|||
sut.NotifyEventsStored("b"); |
|||
|
|||
Assert.Equal(2, handler1Handled); |
|||
Assert.Equal(1, handler2Handled); |
|||
|
|||
Assert.Equal(streamNames.ToArray(), new[] { "a", "b", "b" }); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue