mirror of https://github.com/Squidex/squidex.git
92 changed files with 1622 additions and 2765 deletions
@ -1,15 +1,16 @@ |
|||
// ==========================================================================
|
|||
// IActors.cs
|
|||
// IAppEventConsumer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Actors |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps |
|||
{ |
|||
public interface IActors |
|||
public interface IAppEventConsumer : IEventConsumer |
|||
{ |
|||
IActor Get(string id); |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +1,16 @@ |
|||
// ==========================================================================
|
|||
// IActor.cs
|
|||
// IAssetEventConsumer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Actors |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Assets |
|||
{ |
|||
public interface IActor |
|||
public interface IAssetEventConsumer : IEventConsumer |
|||
{ |
|||
void Tell(object message); |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +1,16 @@ |
|||
// ==========================================================================
|
|||
// StopConsumerMessage.cs
|
|||
// ISchemaEventConsumer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Actors.Messages |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Schemas |
|||
{ |
|||
[TypeName(nameof(StopConsumerMessage))] |
|||
public sealed class StopConsumerMessage |
|||
public interface ISchemaEventConsumer : IEventConsumer |
|||
{ |
|||
} |
|||
} |
|||
@ -1,20 +1,20 @@ |
|||
// ==========================================================================
|
|||
// IRemoteActorChannel.cs
|
|||
// IXmlRepositoryGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Infrastructure.Actors |
|||
namespace Squidex.Domain.Users.DataProtection.Orleans.Grains |
|||
{ |
|||
public interface IRemoteActorChannel |
|||
public interface IXmlRepositoryGrain : IGrainWithStringKey |
|||
{ |
|||
Task SendAsync(string recipient, object message); |
|||
Task<string[]> GetAllElementsAsync(); |
|||
|
|||
void Subscribe(string recipient, Action<object> handler); |
|||
Task StoreElementAsync(string element, string friendlyName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// XmlRepositoryGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Orleans.Providers; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Users.DataProtection.Orleans.Grains.Implementations |
|||
{ |
|||
[StorageProvider(ProviderName = "Default")] |
|||
public sealed class XmlRepositoryGrain : Grain<Dictionary<string, string>>, IXmlRepositoryGrain |
|||
{ |
|||
public Task<string[]> GetAllElementsAsync() |
|||
{ |
|||
return Task.FromResult(State.Values.ToArray()); |
|||
} |
|||
|
|||
public Task StoreElementAsync(string element, string friendlyName) |
|||
{ |
|||
State[friendlyName] = element; |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// OrleansXmlRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Xml.Linq; |
|||
using Microsoft.AspNetCore.DataProtection.Repositories; |
|||
using Orleans; |
|||
using Squidex.Domain.Users.DataProtection.Orleans.Grains; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Users.DataProtection.Orleans |
|||
{ |
|||
public sealed class OrleansXmlRepository : IXmlRepository |
|||
{ |
|||
private readonly Lazy<IXmlRepositoryGrain> grain; |
|||
|
|||
public OrleansXmlRepository(IClusterClient orleans) |
|||
{ |
|||
Guard.NotNull(orleans, nameof(orleans)); |
|||
|
|||
grain = new Lazy<IXmlRepositoryGrain>(() => orleans.GetGrain<IXmlRepositoryGrain>("Default")); |
|||
} |
|||
|
|||
public IReadOnlyCollection<XElement> GetAllElements() |
|||
{ |
|||
return grain.Value.GetAllElementsAsync().ContinueWith(x => x.Result.Select(XElement.Parse).ToList()).Result; |
|||
} |
|||
|
|||
public void StoreElement(XElement element, string friendlyName) |
|||
{ |
|||
grain.Value.StoreElementAsync(element.ToString(), friendlyName).Wait(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
// ==========================================================================
|
|||
// MongoEventConsumerInfo.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
[BsonIgnoreExtraElements] |
|||
public sealed class MongoEventConsumerInfo : IEventConsumerInfo |
|||
{ |
|||
[BsonId] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public string Name { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonIgnoreIfNull] |
|||
public string Error { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonIgnoreIfDefault] |
|||
public bool IsStopped { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public string Position { get; set; } |
|||
} |
|||
} |
|||
@ -1,74 +0,0 @@ |
|||
// ==========================================================================
|
|||
// MongoEventConsumerInfoRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public sealed class MongoEventConsumerInfoRepository : MongoRepositoryBase<MongoEventConsumerInfo>, IEventConsumerInfoRepository |
|||
{ |
|||
private static readonly FieldDefinition<MongoEventConsumerInfo, string> NameField = Fields.Build(x => x.Name); |
|||
private static readonly FieldDefinition<MongoEventConsumerInfo, string> ErrorField = Fields.Build(x => x.Error); |
|||
private static readonly FieldDefinition<MongoEventConsumerInfo, string> PositionField = Fields.Build(x => x.Position); |
|||
private static readonly FieldDefinition<MongoEventConsumerInfo, bool> IsStoppedField = Fields.Build(x => x.IsStopped); |
|||
|
|||
public MongoEventConsumerInfoRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "EventPositions"; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IEventConsumerInfo>> QueryAsync() |
|||
{ |
|||
var entities = await Collection.Find(new BsonDocument()).SortBy(x => x.Name).ToListAsync(); |
|||
|
|||
return entities.OfType<IEventConsumerInfo>().ToList(); |
|||
} |
|||
|
|||
public async Task<IEventConsumerInfo> FindAsync(string consumerName) |
|||
{ |
|||
var entity = await Collection.Find(Filter.Eq(NameField, consumerName)).FirstOrDefaultAsync(); |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
public Task ClearAsync(IEnumerable<string> currentConsumerNames) |
|||
{ |
|||
return Collection.DeleteManyAsync(Filter.Not(Filter.In(NameField, currentConsumerNames))); |
|||
} |
|||
|
|||
public async Task SetAsync(string consumerName, string position, bool isStopped = false, string error = null) |
|||
{ |
|||
try |
|||
{ |
|||
await Collection.UpdateOneAsync(Filter.Eq(NameField, consumerName), |
|||
Update |
|||
.Set(ErrorField, error) |
|||
.Set(PositionField, position) |
|||
.Set(IsStoppedField, isStopped), |
|||
new UpdateOptions { IsUpsert = true }); |
|||
} |
|||
catch (MongoWriteException ex) |
|||
{ |
|||
if (ex.WriteError?.Category != ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
// ==========================================================================
|
|||
// DefaultRemoteActorChannel.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Actors |
|||
{ |
|||
public sealed class DefaultRemoteActorChannel : IRemoteActorChannel |
|||
{ |
|||
private static readonly string ChannelName = typeof(DefaultRemoteActorChannel).Name; |
|||
private readonly IPubSub pubSub; |
|||
private readonly JsonSerializer serializer; |
|||
private readonly TypeNameRegistry typeNameRegistry; |
|||
|
|||
private sealed class Envelope |
|||
{ |
|||
public string Recipient { get; set; } |
|||
|
|||
public string PayloadType { get; set; } |
|||
|
|||
public JToken Payload { get; set; } |
|||
} |
|||
|
|||
public DefaultRemoteActorChannel(IPubSub pubSub, TypeNameRegistry typeNameRegistry, JsonSerializerSettings serializerSettings = null) |
|||
{ |
|||
Guard.NotNull(pubSub, nameof(pubSub)); |
|||
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); |
|||
|
|||
this.pubSub = pubSub; |
|||
|
|||
this.typeNameRegistry = typeNameRegistry; |
|||
|
|||
serializer = JsonSerializer.Create(serializerSettings ?? new JsonSerializerSettings()); |
|||
} |
|||
|
|||
public Task SendAsync(string recipient, object message) |
|||
{ |
|||
Guard.NotNullOrEmpty(recipient, nameof(recipient)); |
|||
Guard.NotNull(message, nameof(message)); |
|||
|
|||
var messageType = typeNameRegistry.GetName(message.GetType()); |
|||
var messageBody = WriteJson(message); |
|||
|
|||
var envelope = new Envelope { Recipient = recipient, Payload = messageBody, PayloadType = messageType }; |
|||
|
|||
pubSub.Publish(ChannelName, JsonConvert.SerializeObject(envelope), true); |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public void Subscribe(string recipient, Action<object> handler) |
|||
{ |
|||
Guard.NotNullOrEmpty(recipient, nameof(recipient)); |
|||
|
|||
pubSub.Subscribe(ChannelName, json => |
|||
{ |
|||
var envelope = JsonConvert.DeserializeObject<Envelope>(json); |
|||
|
|||
if (string.Equals(envelope.Recipient, recipient, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var messageType = typeNameRegistry.GetType(envelope.PayloadType); |
|||
var messageBody = ReadJson(envelope.Payload, messageType); |
|||
|
|||
handler?.Invoke(messageBody); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private object ReadJson(JToken token, Type type) |
|||
{ |
|||
return token.ToObject(type, serializer); |
|||
} |
|||
|
|||
private JToken WriteJson(object value) |
|||
{ |
|||
return JToken.FromObject(value, serializer); |
|||
} |
|||
} |
|||
} |
|||
@ -1,60 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RemoteActors.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Concurrent; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Actors |
|||
{ |
|||
public sealed class RemoteActors : IActors |
|||
{ |
|||
private readonly ConcurrentDictionary<string, IActor> senders = new ConcurrentDictionary<string, IActor>(); |
|||
private readonly ConcurrentDictionary<string, bool> receivers = new ConcurrentDictionary<string, bool>(); |
|||
private readonly IRemoteActorChannel channel; |
|||
|
|||
private sealed class Sender : IActor |
|||
{ |
|||
private readonly IRemoteActorChannel channel; |
|||
private readonly string recipient; |
|||
|
|||
public Sender(IRemoteActorChannel channel, string recipient) |
|||
{ |
|||
this.recipient = recipient; |
|||
|
|||
this.channel = channel; |
|||
} |
|||
|
|||
public void Tell(object message) |
|||
{ |
|||
channel.SendAsync(recipient, message).Forget(); |
|||
} |
|||
} |
|||
|
|||
public RemoteActors(IRemoteActorChannel channel) |
|||
{ |
|||
Guard.NotNull(channel, nameof(channel)); |
|||
|
|||
this.channel = channel; |
|||
} |
|||
|
|||
public IActor Get(string id) |
|||
{ |
|||
Guard.NotNullOrEmpty(id, nameof(id)); |
|||
|
|||
return senders.GetOrAdd(id, k => new Sender(channel, id)); |
|||
} |
|||
|
|||
public void Connect(string id, IActor actor) |
|||
{ |
|||
Guard.NotNullOrEmpty(id, nameof(id)); |
|||
Guard.NotNull(actor, nameof(actor)); |
|||
|
|||
channel.Subscribe(id, actor.Tell); |
|||
} |
|||
} |
|||
} |
|||
@ -1,323 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerActor.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Actors; |
|||
using Squidex.Infrastructure.CQRS.Events.Actors.Messages; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Actors |
|||
{ |
|||
public class EventConsumerActor : DisposableObjectBase, IEventSubscriber, IActor |
|||
{ |
|||
private readonly EventDataFormatter formatter; |
|||
private readonly IEventStore eventStore; |
|||
private readonly IEventConsumerInfoRepository eventConsumerInfoRepository; |
|||
private readonly ISemanticLog log; |
|||
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(1); |
|||
private IEventSubscription currentSubscription; |
|||
private IEventConsumer eventConsumer; |
|||
private bool statusIsRunning = true; |
|||
private string statusPosition; |
|||
private string statusError; |
|||
|
|||
private static Func<IEventStore, IEventSubscriber, string, string, IEventSubscription> DefaultFactory |
|||
{ |
|||
get { return (e, s, t, p) => new RetrySubscription(e, s, t, p); } |
|||
} |
|||
|
|||
public EventConsumerActor( |
|||
EventDataFormatter formatter, |
|||
IEventStore eventStore, |
|||
IEventConsumerInfoRepository eventConsumerInfoRepository, |
|||
ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(log, nameof(log)); |
|||
Guard.NotNull(formatter, nameof(formatter)); |
|||
Guard.NotNull(eventStore, nameof(eventStore)); |
|||
Guard.NotNull(eventConsumerInfoRepository, nameof(eventConsumerInfoRepository)); |
|||
|
|||
this.log = log; |
|||
|
|||
this.formatter = formatter; |
|||
this.eventStore = eventStore; |
|||
this.eventConsumerInfoRepository = eventConsumerInfoRepository; |
|||
} |
|||
|
|||
protected override void DisposeObject(bool disposing) |
|||
{ |
|||
if (disposing) |
|||
{ |
|||
dispatcher.StopAndWaitAsync().Wait(); |
|||
} |
|||
} |
|||
|
|||
protected virtual IEventSubscription CreateSubscription(IEventStore eventStore, string streamFilter, string position) |
|||
{ |
|||
return new RetrySubscription(eventStore, this, streamFilter, position); |
|||
} |
|||
|
|||
public Task SubscribeAsync(IEventConsumer eventConsumer) |
|||
{ |
|||
Guard.NotNull(eventConsumer, nameof(eventConsumer)); |
|||
|
|||
return dispatcher.DispatchAsync(() => HandleSetupAsync(eventConsumer)); |
|||
} |
|||
|
|||
private async Task HandleSetupAsync(IEventConsumer consumer) |
|||
{ |
|||
eventConsumer = consumer; |
|||
|
|||
var status = await eventConsumerInfoRepository.FindAsync(eventConsumer.Name); |
|||
|
|||
if (status != null) |
|||
{ |
|||
statusError = status.Error; |
|||
statusPosition = status.Position; |
|||
statusIsRunning = !status.IsStopped; |
|||
} |
|||
|
|||
if (statusIsRunning) |
|||
{ |
|||
Subscribe(statusPosition); |
|||
} |
|||
} |
|||
|
|||
private Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|||
{ |
|||
if (subscription != currentSubscription) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(async () => |
|||
{ |
|||
var @event = ParseKnownEvent(storedEvent); |
|||
|
|||
if (@event != null) |
|||
{ |
|||
await DispatchConsumerAsync(@event); |
|||
} |
|||
|
|||
statusError = null; |
|||
statusPosition = storedEvent.EventPosition; |
|||
}); |
|||
} |
|||
|
|||
private Task HandleErrorAsync(IEventSubscription subscription, Exception exception) |
|||
{ |
|||
if (subscription != currentSubscription) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(() => |
|||
{ |
|||
Unsubscribe(); |
|||
|
|||
statusError = exception.ToString(); |
|||
statusIsRunning = false; |
|||
}); |
|||
} |
|||
|
|||
private Task HandleStartAsync() |
|||
{ |
|||
if (statusIsRunning) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(() => |
|||
{ |
|||
Subscribe(statusPosition); |
|||
|
|||
statusError = null; |
|||
statusIsRunning = true; |
|||
}); |
|||
} |
|||
|
|||
private Task HandleStopAsync() |
|||
{ |
|||
if (!statusIsRunning) |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
return DoAndUpdateStateAsync(() => |
|||
{ |
|||
Unsubscribe(); |
|||
|
|||
statusError = null; |
|||
statusIsRunning = false; |
|||
}); |
|||
} |
|||
|
|||
private Task HandleResetInternalAsync() |
|||
{ |
|||
return DoAndUpdateStateAsync(async () => |
|||
{ |
|||
Unsubscribe(); |
|||
|
|||
await ClearAsync(); |
|||
|
|||
Subscribe(null); |
|||
|
|||
statusError = null; |
|||
statusPosition = null; |
|||
statusIsRunning = true; |
|||
}); |
|||
} |
|||
|
|||
Task IEventSubscriber.OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|||
{ |
|||
return dispatcher.DispatchAsync(() => HandleEventAsync(subscription, storedEvent)); |
|||
} |
|||
|
|||
Task IEventSubscriber.OnErrorAsync(IEventSubscription subscription, Exception exception) |
|||
{ |
|||
return dispatcher.DispatchAsync(() => HandleErrorAsync(subscription, exception)); |
|||
} |
|||
|
|||
void IActor.Tell(object message) |
|||
{ |
|||
switch (message) |
|||
{ |
|||
case StopConsumerMessage stop: |
|||
dispatcher.DispatchAsync(() => HandleStopAsync()).Forget(); |
|||
break; |
|||
|
|||
case StartConsumerMessage stop: |
|||
dispatcher.DispatchAsync(() => HandleStartAsync()).Forget(); |
|||
break; |
|||
|
|||
case ResetConsumerMessage stop: |
|||
dispatcher.DispatchAsync(() => HandleResetInternalAsync()).Forget(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private Task DoAndUpdateStateAsync(Action action) |
|||
{ |
|||
return DoAndUpdateStateAsync(() => { action(); return TaskHelper.Done; }); |
|||
} |
|||
|
|||
private async Task DoAndUpdateStateAsync(Func<Task> action) |
|||
{ |
|||
try |
|||
{ |
|||
await action(); |
|||
await eventConsumerInfoRepository.SetAsync(eventConsumer.Name, statusPosition, !statusIsRunning, statusError); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
try |
|||
{ |
|||
Unsubscribe(); |
|||
} |
|||
catch (Exception unsubscribeException) |
|||
{ |
|||
ex = new AggregateException(ex, unsubscribeException); |
|||
} |
|||
|
|||
log.LogFatal(ex, w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("state", "Failed") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
statusError = ex.ToString(); |
|||
statusIsRunning = false; |
|||
|
|||
await eventConsumerInfoRepository.SetAsync(eventConsumer.Name, statusPosition, !statusIsRunning, statusError); |
|||
} |
|||
} |
|||
|
|||
private async Task ClearAsync() |
|||
{ |
|||
var actionId = Guid.NewGuid().ToString(); |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Started") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("action", "EventConsumerReset") |
|||
.WriteProperty("actionId", actionId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventConsumer", eventConsumer.Name))) |
|||
{ |
|||
await eventConsumer.ClearAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task DispatchConsumerAsync(Envelope<IEvent> @event) |
|||
{ |
|||
var eventId = @event.Headers.EventId().ToString(); |
|||
var eventType = @event.Payload.GetType().Name; |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("actionId", eventId) |
|||
.WriteProperty("state", "Started") |
|||
.WriteProperty("eventId", eventId) |
|||
.WriteProperty("eventType", eventType) |
|||
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|||
|
|||
using (log.MeasureTrace(w => w |
|||
.WriteProperty("action", "HandleEvent") |
|||
.WriteProperty("actionId", eventId) |
|||
.WriteProperty("state", "Completed") |
|||
.WriteProperty("eventId", eventId) |
|||
.WriteProperty("eventType", eventType) |
|||
.WriteProperty("eventConsumer", eventConsumer.Name))) |
|||
{ |
|||
await eventConsumer.On(@event); |
|||
} |
|||
} |
|||
|
|||
private void Unsubscribe() |
|||
{ |
|||
if (currentSubscription != null) |
|||
{ |
|||
currentSubscription.StopAsync().Forget(); |
|||
currentSubscription = null; |
|||
} |
|||
} |
|||
|
|||
private void Subscribe(string position) |
|||
{ |
|||
if (currentSubscription == null) |
|||
{ |
|||
currentSubscription?.StopAsync().Forget(); |
|||
currentSubscription = CreateSubscription(eventStore, eventConsumer.EventsFilter, position); |
|||
} |
|||
} |
|||
|
|||
private Envelope<IEvent> ParseKnownEvent(StoredEvent message) |
|||
{ |
|||
try |
|||
{ |
|||
var @event = formatter.Parse(message.Data); |
|||
|
|||
@event.SetEventPosition(message.EventPosition); |
|||
@event.SetEventStreamNumber(message.EventStreamNumber); |
|||
|
|||
return @event; |
|||
} |
|||
catch (TypeNameNotFoundException) |
|||
{ |
|||
log.LogDebug(w => w.WriteProperty("oldEventFound", message.Data.Type)); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
// ==========================================================================
|
|||
// StartConsumerMessage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Actors.Messages |
|||
{ |
|||
[TypeName(nameof(StartConsumerMessage))] |
|||
public sealed class StartConsumerMessage |
|||
{ |
|||
} |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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 DefaultEventNotifier(IPubSub pubsub) |
|||
{ |
|||
Guard.NotNull(pubsub, nameof(pubsub)); |
|||
|
|||
this.pubsub = pubsub; |
|||
} |
|||
|
|||
public void NotifyEventsStored(string streamName) |
|||
{ |
|||
pubsub.Publish(ChannelName, streamName, true); |
|||
} |
|||
|
|||
public IDisposable Subscribe(Action<string> handler) |
|||
{ |
|||
return pubsub.Subscribe(ChannelName, x => handler?.Invoke(x)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerCleaner.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public sealed class EventConsumerCleaner |
|||
{ |
|||
private readonly IEnumerable<IEventConsumer> eventConsumers; |
|||
private readonly IEventConsumerInfoRepository eventConsumerInfoRepository; |
|||
|
|||
public EventConsumerCleaner(IEnumerable<IEventConsumer> eventConsumers, IEventConsumerInfoRepository eventConsumerInfoRepository) |
|||
{ |
|||
Guard.NotNull(eventConsumers, nameof(eventConsumers)); |
|||
Guard.NotNull(eventConsumerInfoRepository, nameof(eventConsumerInfoRepository)); |
|||
|
|||
this.eventConsumers = eventConsumers; |
|||
this.eventConsumerInfoRepository = eventConsumerInfoRepository; |
|||
} |
|||
|
|||
public Task CleanAsync() |
|||
{ |
|||
var names = eventConsumers.Select(x => x.Name).ToArray(); |
|||
|
|||
return eventConsumerInfoRepository.ClearAsync(names); |
|||
} |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerRegistryGrainState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Grains.Implementation |
|||
{ |
|||
public sealed class EventConsumerRegistryGrainState |
|||
{ |
|||
public HashSet<string> EventConsumerNames { get; set; } |
|||
|
|||
public EventConsumerRegistryGrainState() |
|||
{ |
|||
EventConsumerNames = new HashSet<string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEventConsumerInfo.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public interface IEventConsumerInfo |
|||
{ |
|||
bool IsStopped { get; } |
|||
|
|||
string Name { get; } |
|||
|
|||
string Error { get; } |
|||
|
|||
string Position { get; } |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEventConsumerInfoRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public interface IEventConsumerInfoRepository |
|||
{ |
|||
Task<IReadOnlyList<IEventConsumerInfo>> QueryAsync(); |
|||
|
|||
Task<IEventConsumerInfo> FindAsync(string consumerName); |
|||
|
|||
Task ClearAsync(IEnumerable<string> currentConsumerNames); |
|||
|
|||
Task SetAsync(string consumerName, string position, bool isStopped, string error = null); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerBootstrap.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Orleans.Providers; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains |
|||
{ |
|||
public sealed class EventConsumerBootstrap : IBootstrapProvider |
|||
{ |
|||
public string Name { get; private set; } |
|||
|
|||
public Task Close() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public Task Init(string name, IProviderRuntime providerRuntime, IProviderConfiguration config) |
|||
{ |
|||
Name = name; |
|||
|
|||
return providerRuntime.GrainFactory.GetGrain<IEventConsumerRegistryGrain>("Default").ActivateAsync(null); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerGrainState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|||
{ |
|||
public sealed class EventConsumerGrainState |
|||
{ |
|||
public bool IsStopped { get; set; } |
|||
|
|||
public string Error { get; set; } |
|||
|
|||
public string Position { get; set; } |
|||
|
|||
public static EventConsumerGrainState Initial() |
|||
{ |
|||
return new EventConsumerGrainState(); |
|||
} |
|||
|
|||
public static EventConsumerGrainState Handled(string position) |
|||
{ |
|||
return new EventConsumerGrainState { Position = position }; |
|||
} |
|||
|
|||
public static EventConsumerGrainState Failed(Exception ex) |
|||
{ |
|||
return new EventConsumerGrainState { IsStopped = true, Error = ex?.ToString() }; |
|||
} |
|||
|
|||
public EventConsumerGrainState Stopped() |
|||
{ |
|||
return new EventConsumerGrainState { Position = Position, IsStopped = true }; |
|||
} |
|||
|
|||
public EventConsumerGrainState Started() |
|||
{ |
|||
return new EventConsumerGrainState { Position = Position, IsStopped = false }; |
|||
} |
|||
|
|||
public EventConsumerInfo ToInfo(string name) |
|||
{ |
|||
return SimpleMapper.Map(this, new EventConsumerInfo { Name = name }); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// OrleansClientEventNotifier.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Orleans; |
|||
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Orleans |
|||
{ |
|||
public sealed class OrleansClientEventNotifier : IEventNotifier |
|||
{ |
|||
private readonly IEventConsumerRegistryGrain eventConsumerRegistryGrain; |
|||
|
|||
public OrleansClientEventNotifier(IClusterClient orleans) |
|||
{ |
|||
Guard.NotNull(orleans, nameof(orleans)); |
|||
|
|||
eventConsumerRegistryGrain = orleans.GetGrain<IEventConsumerRegistryGrain>("Default"); |
|||
} |
|||
|
|||
public void NotifyEventsStored(string streamName) |
|||
{ |
|||
eventConsumerRegistryGrain.ActivateAsync(streamName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// OrleansEventNotifier.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Orleans; |
|||
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Orleans |
|||
{ |
|||
public sealed class OrleansSiloEventNotifier : IEventNotifier |
|||
{ |
|||
private readonly IEventConsumerRegistryGrain eventConsumerRegistryGrain; |
|||
|
|||
public OrleansSiloEventNotifier(IGrainFactory orleans) |
|||
{ |
|||
Guard.NotNull(orleans, nameof(orleans)); |
|||
|
|||
eventConsumerRegistryGrain = orleans.GetGrain<IEventConsumerRegistryGrain>("Default"); |
|||
} |
|||
|
|||
public void NotifyEventsStored(string streamName) |
|||
{ |
|||
eventConsumerRegistryGrain.ActivateAsync(streamName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// ==========================================================================
|
|||
// AppConfiguration.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
|
|||
namespace Squidex |
|||
{ |
|||
public static class AppConfiguration |
|||
{ |
|||
public static void AddAppConfiguration(this IConfigurationBuilder builder, string environmentName, string[] args) |
|||
{ |
|||
builder.Sources.Clear(); |
|||
|
|||
builder.AddJsonFile("appsettings.json", true, true); |
|||
builder.AddJsonFile($"appsettings.{environmentName}.json", true); |
|||
|
|||
builder.AddEnvironmentVariables(); |
|||
|
|||
builder.AddCommandLine(args); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// ==========================================================================
|
|||
// Services.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Config; |
|||
using Squidex.Config.Domain; |
|||
using Squidex.Config.Identity; |
|||
using Squidex.Config.Orleans; |
|||
using Squidex.Config.Swagger; |
|||
using Squidex.Config.Web; |
|||
|
|||
namespace Squidex |
|||
{ |
|||
public static class AppServices |
|||
{ |
|||
public static void AddAppServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
services.AddLogging(); |
|||
services.AddMemoryCache(); |
|||
services.AddOptions(); |
|||
|
|||
services.AddMyAssetServices(config); |
|||
services.AddMyAuthentication(config); |
|||
services.AddMyDataProtectection(config); |
|||
services.AddMyEventPublishersServices(config); |
|||
services.AddMyEventStoreServices(config); |
|||
services.AddMyIdentity(); |
|||
services.AddMyIdentityServer(); |
|||
services.AddMyInfrastructureServices(config); |
|||
services.AddMyMvc(); |
|||
services.AddMyPubSubServices(config); |
|||
services.AddMyReadServices(config); |
|||
services.AddMySerializers(); |
|||
services.AddMyStoreServices(config); |
|||
services.AddMySwaggerSettings(); |
|||
services.AddMyWriteServices(); |
|||
|
|||
services.Configure<MyUrlsOptions>( |
|||
config.GetSection("urls")); |
|||
services.Configure<MyIdentityOptions>( |
|||
config.GetSection("identity")); |
|||
services.Configure<MyUIOptions>( |
|||
config.GetSection("ui")); |
|||
services.Configure<MyUsageOptions>( |
|||
config.GetSection("usage")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// ==========================================================================
|
|||
// AssetServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class AssetServices |
|||
{ |
|||
public static void AddMyAssetServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
config.ConfigureByOption("assetStore:type", new Options |
|||
{ |
|||
["Folder"] = () => |
|||
{ |
|||
var path = config.GetRequiredValue("assetStore:folder:path"); |
|||
|
|||
services.AddSingleton(c => new FolderAssetStore(path, c.GetRequiredService<ISemanticLog>())) |
|||
.As<IAssetStore>() |
|||
.As<IExternalSystem>(); |
|||
}, |
|||
["GoogleCloud"] = () => |
|||
{ |
|||
var bucketName = config.GetRequiredValue("assetStore:googleCloud:bucket"); |
|||
|
|||
services.AddSingleton(c => new GoogleCloudAssetStore(bucketName)) |
|||
.As<IAssetStore>() |
|||
.As<IExternalSystem>(); |
|||
}, |
|||
["AzureBlob"] = () => |
|||
{ |
|||
var connectionString = config.GetRequiredValue("assetStore:azureBlob:connectionString"); |
|||
var containerName = config.GetRequiredValue("assetStore:azureBlob:containerName"); |
|||
|
|||
services.AddSingleton(c => new AzureBlobAssetStore(connectionString, containerName)) |
|||
.As<IAssetStore>() |
|||
.As<IExternalSystem>(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,91 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AssetStoreModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public sealed class AssetStoreModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public AssetStoreModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var assetStoreType = Configuration.GetValue<string>("assetStore:type"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(assetStoreType)) |
|||
{ |
|||
throw new ConfigurationException("Configure the AssetStore type with 'assetStore:type'."); |
|||
} |
|||
|
|||
if (string.Equals(assetStoreType, "Folder", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var path = Configuration.GetValue<string>("assetStore:folder:path"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(path)) |
|||
{ |
|||
throw new ConfigurationException("Configure AssetStore Folder path with 'assetStore:folder:path'."); |
|||
} |
|||
|
|||
builder.Register(c => new FolderAssetStore(path, c.Resolve<ISemanticLog>())) |
|||
.As<IAssetStore>() |
|||
.As<IExternalSystem>() |
|||
.SingleInstance(); |
|||
} |
|||
else if (string.Equals(assetStoreType, "GoogleCloud", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var bucketName = Configuration.GetValue<string>("assetStore:googleCloud:bucket"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(bucketName)) |
|||
{ |
|||
throw new ConfigurationException("Configure AssetStore GoogleCloud bucket with 'assetStore:googleCloud:bucket'."); |
|||
} |
|||
|
|||
builder.Register(c => new GoogleCloudAssetStore(bucketName)) |
|||
.As<IAssetStore>() |
|||
.As<IExternalSystem>() |
|||
.SingleInstance(); |
|||
} |
|||
else if (string.Equals(assetStoreType, "AzureBlob", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var connectionString = Configuration.GetValue<string>("assetStore:azureBlob:connectionString"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(connectionString)) |
|||
{ |
|||
throw new ConfigurationException("Configure AssetStore AzureBlob connection string with 'assetStore:azureBlob:connectionString'."); |
|||
} |
|||
|
|||
var containerName = Configuration.GetValue<string>("assetStore:azureBlob:containerName"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(containerName)) |
|||
{ |
|||
throw new ConfigurationException("Configure AssetStore AzureBlob container with 'assetStore:azureBlob:containerName'."); |
|||
} |
|||
|
|||
builder.Register(c => new AzureBlobAssetStore(connectionString, containerName)) |
|||
.As<IAssetStore>() |
|||
.As<IExternalSystem>() |
|||
.SingleInstance(); |
|||
} |
|||
else |
|||
{ |
|||
throw new ConfigurationException($"Unsupported value '{assetStoreType}' for 'assetStore:type', supported: AzureBlob, Folder, GoogleCloud."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,112 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventStoreModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Autofac; |
|||
using Autofac.Core; |
|||
using EventStore.ClientAPI; |
|||
using Microsoft.Extensions.Configuration; |
|||
using MongoDB.Driver; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.CQRS.Events.Actors; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public sealed class EventStoreModule : Module |
|||
{ |
|||
private const string MongoClientRegistration = "EventStoreMongoClient"; |
|||
private const string MongoDatabaseRegistration = "EventStoreMongoDatabase"; |
|||
|
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public EventStoreModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var consumeEvents = Configuration.GetValue<bool>("eventStore:consume"); |
|||
|
|||
if (consumeEvents) |
|||
{ |
|||
builder.RegisterType<EventConsumerActor>() |
|||
.AsSelf() |
|||
.InstancePerDependency(); |
|||
} |
|||
|
|||
var eventStoreType = Configuration.GetValue<string>("eventStore:type"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(eventStoreType)) |
|||
{ |
|||
throw new ConfigurationException("Configure EventStore type with 'eventStore:type'."); |
|||
} |
|||
|
|||
if (string.Equals(eventStoreType, "MongoDb", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var configuration = Configuration.GetValue<string>("eventStore:mongoDb:configuration"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(configuration)) |
|||
{ |
|||
throw new ConfigurationException("Configure EventStore MongoDb configuration with 'eventStore:mongoDb:configuration'."); |
|||
} |
|||
|
|||
var database = Configuration.GetValue<string>("eventStore:mongoDb:database"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(database)) |
|||
{ |
|||
throw new ConfigurationException("Configure EventStore MongoDb Database name with 'eventStore:mongoDb:database'."); |
|||
} |
|||
|
|||
builder.Register(c => Singletons<IMongoClient>.GetOrAdd(configuration, s => new MongoClient(s))) |
|||
.Named<IMongoClient>(MongoClientRegistration) |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => c.ResolveNamed<IMongoClient>(MongoClientRegistration).GetDatabase(database)) |
|||
.Named<IMongoDatabase>(MongoDatabaseRegistration) |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoEventStore>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IExternalSystem>() |
|||
.As<IEventStore>() |
|||
.SingleInstance(); |
|||
} |
|||
else if (string.Equals(eventStoreType, "GetEventStore", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var configuration = Configuration.GetValue<string>("eventStore:getEventStore:configuration"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(configuration)) |
|||
{ |
|||
throw new ConfigurationException("Configure GetEventStore EventStore configuration with 'eventStore:getEventStore:configuration'."); |
|||
} |
|||
|
|||
var projectionHost = Configuration.GetValue<string>("eventStore:getEventStore:projectionHost"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(projectionHost)) |
|||
{ |
|||
throw new ConfigurationException("Configure GetEventStore EventStore projection host with 'eventStore:getEventStore:projectionHost'."); |
|||
} |
|||
|
|||
var prefix = Configuration.GetValue<string>("eventStore:getEventStore:prefix"); |
|||
|
|||
var connection = EventStoreConnection.Create(configuration); |
|||
|
|||
builder.Register(c => new GetEventStore(connection, prefix, projectionHost)) |
|||
.As<IExternalSystem>() |
|||
.As<IEventStore>() |
|||
.SingleInstance(); |
|||
} |
|||
else |
|||
{ |
|||
throw new ConfigurationException($"Unsupported value '{eventStoreType}' for 'eventStore:type', supported: MongoDb, GetEventStore."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// ==========================================================================
|
|||
// EventStoreServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using EventStore.ClientAPI; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using MongoDB.Driver; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class EventStoreServices |
|||
{ |
|||
public static void AddMyEventStoreServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
var consumeEvents = config.GetOptionalValue("eventStore:consume", false); |
|||
|
|||
if (!consumeEvents) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
config.ConfigureByOption("eventStore:type", new Options |
|||
{ |
|||
["MongoDb"] = () => |
|||
{ |
|||
var mongoConfiguration = config.GetRequiredValue("eventStore:mongoDb:configuration"); |
|||
var mongoDatabaseName = config.GetRequiredValue("eventStore:mongoDb:database"); |
|||
|
|||
services.AddSingleton(c => |
|||
{ |
|||
var mongoClient = Singletons<IMongoClient>.GetOrAdd(mongoConfiguration, s => new MongoClient(s)); |
|||
var mongDatabase = mongoClient.GetDatabase(mongoDatabaseName); |
|||
|
|||
return new MongoEventStore(mongDatabase, c.GetRequiredService<IEventNotifier>()); |
|||
}) |
|||
.As<IExternalSystem>() |
|||
.As<IEventStore>(); |
|||
}, |
|||
["GetEventStore"] = () => |
|||
{ |
|||
var eventStoreConfiguration = config.GetRequiredValue("eventStore:getEventStore:configuration"); |
|||
var eventStoreProjectionHost = config.GetRequiredValue("eventStore:getEventStore:projectionHost"); |
|||
var eventStorePrefix = config.GetValue<string>("eventStore:getEventStore:prefix"); |
|||
|
|||
var connection = EventStoreConnection.Create(eventStoreConfiguration); |
|||
|
|||
services.AddSingleton(c => new GetEventStore(connection, eventStorePrefix, eventStoreProjectionHost)) |
|||
.As<IExternalSystem>() |
|||
.As<IEventStore>(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,155 +0,0 @@ |
|||
// ==========================================================================
|
|||
// InfrastructureModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Autofac; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc.Infrastructure; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Options; |
|||
using Newtonsoft.Json; |
|||
using NodaTime; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Actors; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.Assets.ImageSharp; |
|||
using Squidex.Infrastructure.Caching; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.UsageTracking; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public sealed class InfrastructureModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public InfrastructureModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
if (Configuration.GetValue<bool>("logging:human")) |
|||
{ |
|||
builder.Register(c => new Func<IObjectWriter>(() => new JsonLogWriter(Formatting.Indented, true))) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
else |
|||
{ |
|||
builder.Register(c => new Func<IObjectWriter>(() => new JsonLogWriter())) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
|
|||
var loggingFile = Configuration.GetValue<string>("logging:file"); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(loggingFile)) |
|||
{ |
|||
builder.RegisterInstance(new FileChannel(loggingFile)) |
|||
.As<ILogChannel>() |
|||
.As<IExternalSystem>() |
|||
.SingleInstance(); |
|||
} |
|||
|
|||
builder.Register(c => new ApplicationInfoLogAppender(GetType(), Guid.NewGuid())) |
|||
.As<ILogAppender>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ActionContextLogAppender>() |
|||
.As<ILogAppender>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<TimestampLogAppender>() |
|||
.As<ILogAppender>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<DebugLogChannel>() |
|||
.As<ILogChannel>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ConsoleLogChannel>() |
|||
.As<ILogChannel>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<SemanticLog>() |
|||
.As<ISemanticLog>() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => SystemClock.Instance) |
|||
.As<IClock>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<BackgroundUsageTracker>() |
|||
.As<IUsageTracker>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<HttpContextAccessor>() |
|||
.As<IHttpContextAccessor>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ActionContextAccessor>() |
|||
.As<IActionContextAccessor>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<DefaultDomainObjectRepository>() |
|||
.As<IDomainObjectRepository>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<DefaultDomainObjectFactory>() |
|||
.As<IDomainObjectFactory>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<AggregateHandler>() |
|||
.As<IAggregateHandler>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<InMemoryCommandBus>() |
|||
.As<ICommandBus>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<DefaultEventNotifier>() |
|||
.As<IEventNotifier>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<DefaultStreamNameResolver>() |
|||
.As<IStreamNameResolver>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ImageSharpAssetThumbnailGenerator>() |
|||
.As<IAssetThumbnailGenerator>() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => new InvalidatingMemoryCache(new MemoryCache(c.Resolve<IOptions<MemoryCacheOptions>>()), c.Resolve<IPubSub>())) |
|||
.As<IMemoryCache>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<DefaultRemoteActorChannel>() |
|||
.As<IRemoteActorChannel>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<RemoteActors>() |
|||
.As<IActors>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EventConsumerCleaner>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EventDataFormatter>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,110 @@ |
|||
// ==========================================================================
|
|||
// InfrastructureServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc.Infrastructure; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Newtonsoft.Json; |
|||
using NodaTime; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.Assets.ImageSharp; |
|||
using Squidex.Infrastructure.Caching; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.CQRS.Events.Orleans; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.UsageTracking; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class InfrastructureServices |
|||
{ |
|||
public static void AddMyInfrastructureServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
if (config.GetValue<bool>("logging:human")) |
|||
{ |
|||
services.AddSingleton(c => new Func<IObjectWriter>(() => new JsonLogWriter(Formatting.Indented, true))); |
|||
} |
|||
else |
|||
{ |
|||
services.AddSingleton(c => new Func<IObjectWriter>(() => new JsonLogWriter())); |
|||
} |
|||
|
|||
var loggingFile = config.GetValue<string>("logging:file"); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(loggingFile)) |
|||
{ |
|||
services.AddSingleton(new FileChannel(loggingFile)) |
|||
.As<ILogChannel>() |
|||
.As<IExternalSystem>(); |
|||
} |
|||
|
|||
services.AddSingleton(c => new ApplicationInfoLogAppender(typeof(Program).Assembly, Guid.NewGuid())) |
|||
.As<ILogAppender>(); |
|||
|
|||
services.AddSingleton<ActionContextLogAppender>() |
|||
.As<ILogAppender>(); |
|||
|
|||
services.AddSingleton<TimestampLogAppender>() |
|||
.As<ILogAppender>(); |
|||
|
|||
services.AddSingleton<DebugLogChannel>() |
|||
.As<ILogChannel>(); |
|||
|
|||
services.AddSingleton<ConsoleLogChannel>() |
|||
.As<ILogChannel>(); |
|||
|
|||
services.AddSingleton<SemanticLog>() |
|||
.As<ISemanticLog>(); |
|||
|
|||
services.AddSingleton(SystemClock.Instance) |
|||
.As<IClock>(); |
|||
|
|||
services.AddSingleton<BackgroundUsageTracker>() |
|||
.As<IUsageTracker>(); |
|||
|
|||
services.AddSingleton<HttpContextAccessor>() |
|||
.As<IHttpContextAccessor>(); |
|||
|
|||
services.AddSingleton<ActionContextAccessor>() |
|||
.As<IActionContextAccessor>(); |
|||
|
|||
services.AddSingleton<DefaultDomainObjectRepository>() |
|||
.As<IDomainObjectRepository>(); |
|||
|
|||
services.AddSingleton<DefaultDomainObjectFactory>() |
|||
.As<IDomainObjectFactory>(); |
|||
|
|||
services.AddSingleton<AggregateHandler>() |
|||
.As<IAggregateHandler>(); |
|||
|
|||
services.AddSingleton<InMemoryCommandBus>() |
|||
.As<ICommandBus>(); |
|||
|
|||
services.AddSingleton<DefaultStreamNameResolver>() |
|||
.As<IStreamNameResolver>(); |
|||
|
|||
services.AddSingleton<ImageSharpAssetThumbnailGenerator>() |
|||
.As<IAssetThumbnailGenerator>(); |
|||
|
|||
services.AddSingleton<EventDataFormatter>(); |
|||
|
|||
services.AddSingleton(c => new InvalidatingMemoryCache( |
|||
new MemoryCache( |
|||
c.GetRequiredService<IOptions<MemoryCacheOptions>>()), |
|||
c.GetRequiredService<IPubSub>())) |
|||
.As<IMemoryCache>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// ==========================================================================
|
|||
// LoggingExtensions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class LoggingExtensions |
|||
{ |
|||
public static void LogConfiguration(this IServiceProvider services) |
|||
{ |
|||
var log = services.GetRequiredService<ISemanticLog>(); |
|||
|
|||
var config = services.GetRequiredService<IConfiguration>(); |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("message", "Application started") |
|||
.WriteObject("environment", c => |
|||
{ |
|||
foreach (var kvp in config.AsEnumerable().Where(kvp => kvp.Value != null)) |
|||
{ |
|||
c.WriteProperty(kvp.Key, kvp.Value); |
|||
} |
|||
})); |
|||
} |
|||
} |
|||
} |
|||
@ -1,69 +0,0 @@ |
|||
// ==========================================================================
|
|||
// PubSubModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Autofac; |
|||
using Autofac.Core; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Squidex.Infrastructure; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public sealed class PubSubModule : Module |
|||
{ |
|||
private const string RedisRegistration = "PubSubRedis"; |
|||
|
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public PubSubModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var pubSubType = Configuration.GetValue<string>("pubSub:type"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(pubSubType)) |
|||
{ |
|||
throw new ConfigurationException("Configure the PubSub type with 'pubSub:type'."); |
|||
} |
|||
|
|||
if (string.Equals(pubSubType, "Redis", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
var configuration = Configuration.GetValue<string>("pubsub:redis:configuration"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(configuration)) |
|||
{ |
|||
throw new ConfigurationException("Configure PubSub Redis configuration with 'pubSub:redis:configuration'."); |
|||
} |
|||
|
|||
builder.Register(c => Singletons<IConnectionMultiplexer>.GetOrAddLazy(configuration, s => ConnectionMultiplexer.Connect(s))) |
|||
.Named<Lazy<IConnectionMultiplexer>>(RedisRegistration) |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<RedisPubSub>() |
|||
.WithParameter(ResolvedParameter.ForNamed<Lazy<IConnectionMultiplexer>>(RedisRegistration)) |
|||
.As<IPubSub>() |
|||
.As<IExternalSystem>() |
|||
.SingleInstance(); |
|||
} |
|||
else if (string.Equals(pubSubType, "InMemory", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
builder.RegisterType<InMemoryPubSub>() |
|||
.As<IPubSub>() |
|||
.SingleInstance(); |
|||
} |
|||
else |
|||
{ |
|||
throw new ConfigurationException($"Unsupported value '{pubSubType}' for 'pubSub:type', supported: Redis, InMemory."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// PubSubServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class PubSubServices |
|||
{ |
|||
public static void AddMyPubSubServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
config.ConfigureByOption("pubSub:type", new Options |
|||
{ |
|||
["InMemory"] = () => |
|||
{ |
|||
services.AddSingleton<InMemoryPubSub>() |
|||
.As<IPubSub>(); |
|||
}, |
|||
["Redis"] = () => |
|||
{ |
|||
var configuration = config.GetRequiredValue("pubsub:redis:configuration"); |
|||
|
|||
var redis = Singletons<IConnectionMultiplexer>.GetOrAddLazy(configuration, s => ConnectionMultiplexer.Connect(s)); |
|||
|
|||
services.AddSingleton(c => new RedisPubSub(redis, c.GetRequiredService<ISemanticLog>())) |
|||
.As<IPubSub>() |
|||
.As<IExternalSystem>(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,148 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ReadModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Domain.Apps.Core.HandleRules; |
|||
using Squidex.Domain.Apps.Core.HandleRules.ActionHandlers; |
|||
using Squidex.Domain.Apps.Core.HandleRules.Triggers; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps.Services; |
|||
using Squidex.Domain.Apps.Read.Apps.Services.Implementations; |
|||
using Squidex.Domain.Apps.Read.Contents; |
|||
using Squidex.Domain.Apps.Read.Contents.Edm; |
|||
using Squidex.Domain.Apps.Read.Contents.GraphQL; |
|||
using Squidex.Domain.Apps.Read.History; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public sealed class ReadModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public ReadModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
builder.Register(c => c.Resolve<IOptions<MyUsageOptions>>().Value?.Plans ?? Enumerable.Empty<ConfigAppLimitsPlan>()) |
|||
.As<IEnumerable<ConfigAppLimitsPlan>>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => new GraphQLUrlGenerator( |
|||
c.Resolve<IOptions<MyUrlsOptions>>(), |
|||
c.Resolve<IAssetStore>(), |
|||
Configuration.GetValue<bool>("assetStore:exposeSourceUrl"))) |
|||
.As<IGraphQLUrlGenerator>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<CachingGraphQLService>() |
|||
.As<IGraphQLService>() |
|||
.AsSelf() |
|||
.InstancePerDependency(); |
|||
|
|||
builder.RegisterType<ContentQueryService>() |
|||
.As<IContentQueryService>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<CachingAppProvider>() |
|||
.As<IAppProvider>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ConfigAppPlansProvider>() |
|||
.As<IAppPlansProvider>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<CachingSchemaProvider>() |
|||
.As<ISchemaProvider>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<AssetUserPictureStore>() |
|||
.As<IUserPictureStore>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<AppHistoryEventsCreator>() |
|||
.As<IHistoryEventsCreator>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ContentHistoryEventsCreator>() |
|||
.As<IHistoryEventsCreator>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<SchemaHistoryEventsCreator>() |
|||
.As<IHistoryEventsCreator>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<NoopAppPlanBillingManager>() |
|||
.As<IAppPlanBillingManager>() |
|||
.AsSelf() |
|||
.InstancePerDependency(); |
|||
|
|||
builder.RegisterType<RuleDequeuer>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.InstancePerDependency(); |
|||
|
|||
builder.RegisterType<RuleEnqueuer>() |
|||
.As<IEventConsumer>() |
|||
.AsSelf() |
|||
.InstancePerDependency(); |
|||
|
|||
builder.RegisterType<ContentChangedTriggerHandler>() |
|||
.As<IRuleTriggerHandler>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<WebhookActionHandler>() |
|||
.As<IRuleActionHandler>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<RuleService>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EdmModelBuilder>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => |
|||
{ |
|||
var eventConsumers = c.Resolve<IEnumerable<IEventConsumer>>(); |
|||
|
|||
return new EventConsumerFactory(x => eventConsumers.First(e => e.Name == x)); |
|||
}) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
// ==========================================================================
|
|||
// ReadServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Domain.Apps.Core.HandleRules; |
|||
using Squidex.Domain.Apps.Core.HandleRules.ActionHandlers; |
|||
using Squidex.Domain.Apps.Core.HandleRules.Triggers; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps.Services; |
|||
using Squidex.Domain.Apps.Read.Apps.Services.Implementations; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Domain.Apps.Read.Contents; |
|||
using Squidex.Domain.Apps.Read.Contents.Edm; |
|||
using Squidex.Domain.Apps.Read.Contents.GraphQL; |
|||
using Squidex.Domain.Apps.Read.History; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class ReadServices |
|||
{ |
|||
public static void AddMyReadServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true); |
|||
|
|||
services.AddSingleton(c => new GraphQLUrlGenerator( |
|||
c.GetRequiredService<IOptions<MyUrlsOptions>>(), |
|||
c.GetRequiredService<IAssetStore>(), |
|||
exposeSourceUrl)) |
|||
.As<IGraphQLUrlGenerator>(); |
|||
|
|||
services.AddSingleton(c => c.GetService<IOptions<MyUsageOptions>>()?.Value?.Plans.OrEmpty()); |
|||
|
|||
services.AddSingleton<CachingGraphQLService>() |
|||
.As<IGraphQLService>(); |
|||
|
|||
services.AddSingleton<ContentQueryService>() |
|||
.As<IContentQueryService>(); |
|||
|
|||
services.AddSingleton<CachingAppProvider>() |
|||
.As<IAppProvider>(); |
|||
|
|||
services.AddSingleton<ConfigAppPlansProvider>() |
|||
.As<IAppPlansProvider>(); |
|||
|
|||
services.AddSingleton<CachingSchemaProvider>() |
|||
.As<ISchemaProvider>(); |
|||
|
|||
services.AddSingleton<AssetUserPictureStore>() |
|||
.As<IUserPictureStore>(); |
|||
|
|||
services.AddSingleton<AppHistoryEventsCreator>() |
|||
.As<IHistoryEventsCreator>(); |
|||
|
|||
services.AddSingleton<ContentHistoryEventsCreator>() |
|||
.As<IHistoryEventsCreator>(); |
|||
|
|||
services.AddSingleton<SchemaHistoryEventsCreator>() |
|||
.As<IHistoryEventsCreator>(); |
|||
|
|||
services.AddSingleton<NoopAppPlanBillingManager>() |
|||
.As<IAppPlanBillingManager>(); |
|||
|
|||
services.AddSingleton<RuleDequeuer>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton<RuleEnqueuer>() |
|||
.As<IEventConsumer>(); |
|||
|
|||
services.AddSingleton<ContentChangedTriggerHandler>() |
|||
.As<IRuleTriggerHandler>(); |
|||
|
|||
services.AddSingleton<WebhookActionHandler>() |
|||
.As<IRuleActionHandler>(); |
|||
|
|||
services.AddSingleton(c => new CompoundEventConsumer(c.GetServices<IAssetEventConsumer>().ToArray())) |
|||
.As<IEventConsumer>(); |
|||
|
|||
services.AddSingleton(c => |
|||
new CompoundEventConsumer( |
|||
c.GetServices<IAppEventConsumer>().OfType<IEventConsumer>() |
|||
.Concat(c.GetRequiredService<CachingAppProvider>()).ToArray())) |
|||
.As<IEventConsumer>(); |
|||
|
|||
services.AddSingleton(c => |
|||
new CompoundEventConsumer( |
|||
c.GetServices<ISchemaEventConsumer>().OfType<IEventConsumer>() |
|||
.Concat(c.GetRequiredService<CachingGraphQLService>()) |
|||
.Concat(c.GetRequiredService<CachingSchemaProvider>()).ToArray())) |
|||
.As<IEventConsumer>(); |
|||
|
|||
services.AddSingleton(c => |
|||
{ |
|||
var allEventConsumers = c.GetServices<IEventConsumer>(); |
|||
|
|||
return new EventConsumerFactory(n => allEventConsumers.FirstOrDefault(x => x.Name == n)); |
|||
}); |
|||
|
|||
services.AddSingleton<RuleService>(); |
|||
services.AddSingleton<EdmModelBuilder>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,44 +0,0 @@ |
|||
// ==========================================================================
|
|||
// StoreModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public class StoreModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public StoreModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var storeType = Configuration.GetValue<string>("store:type"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(storeType)) |
|||
{ |
|||
throw new ConfigurationException("Configure the Store type with 'store:type'."); |
|||
} |
|||
|
|||
if (string.Equals(storeType, "MongoDB", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
builder.RegisterModule(new StoreMongoDbModule(Configuration)); |
|||
} |
|||
else |
|||
{ |
|||
throw new ConfigurationException($"Unsupported value '{storeType}' for 'stores:type', supported: MongoDb."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,208 +0,0 @@ |
|||
// ==========================================================================
|
|||
// StoreMongoDbModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Autofac; |
|||
using Autofac.Core; |
|||
using IdentityServer4.Stores; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Configuration; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Read.Apps.Repositories; |
|||
using Squidex.Domain.Apps.Read.Apps.Services.Implementations; |
|||
using Squidex.Domain.Apps.Read.Assets.Repositories; |
|||
using Squidex.Domain.Apps.Read.Contents.GraphQL; |
|||
using Squidex.Domain.Apps.Read.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Read.History.Repositories; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Apps; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Assets; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Contents; |
|||
using Squidex.Domain.Apps.Read.MongoDb.History; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Rules; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Schemas; |
|||
using Squidex.Domain.Apps.Read.Rules.Repositories; |
|||
using Squidex.Domain.Apps.Read.Schemas.Repositories; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Domain.Users.MongoDb; |
|||
using Squidex.Domain.Users.MongoDb.Infrastructure; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.UsageTracking; |
|||
using Squidex.Shared.Users; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public class StoreMongoDbModule : Module |
|||
{ |
|||
private const string MongoClientRegistration = "StoreMongoClient"; |
|||
private const string MongoDatabaseRegistration = "StoreMongoDatabaseName"; |
|||
private const string MongoContentDatabaseRegistration = "StoreMongoDatabaseNameContent"; |
|||
|
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public StoreMongoDbModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var configuration = Configuration.GetValue<string>("store:mongoDb:configuration"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(configuration)) |
|||
{ |
|||
throw new ConfigurationException("Configure the Store MongoDb configuration with 'store:mongoDb:configuration'."); |
|||
} |
|||
|
|||
var database = Configuration.GetValue<string>("store:mongoDb:database"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(database)) |
|||
{ |
|||
throw new ConfigurationException("Configure the Store MongoDb database with 'store:mongoDb:database'."); |
|||
} |
|||
|
|||
var contentDatabase = Configuration.GetValue<string>("store:mongoDb:contentDatabase"); |
|||
|
|||
if (string.IsNullOrWhiteSpace(contentDatabase)) |
|||
{ |
|||
contentDatabase = database; |
|||
} |
|||
|
|||
builder.Register(c => Singletons<IMongoClient>.GetOrAdd(configuration, s => new MongoClient(s))) |
|||
.Named<IMongoClient>(MongoClientRegistration) |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => c.ResolveNamed<IMongoClient>(MongoClientRegistration).GetDatabase(database)) |
|||
.Named<IMongoDatabase>(MongoDatabaseRegistration) |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => c.ResolveNamed<IMongoClient>(MongoClientRegistration).GetDatabase(contentDatabase)) |
|||
.Named<IMongoDatabase>(MongoContentDatabaseRegistration) |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoUserStore>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IUserStore<IUser>>() |
|||
.As<IUserFactory>() |
|||
.As<IUserResolver>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoRoleStore>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IRoleStore<IRole>>() |
|||
.As<IRoleFactory>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoPersistedGrantStore>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IPersistedGrantStore>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoUsageStore>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IUsageStore>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoHistoryEventRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IHistoryEventRepository>() |
|||
.As<IEventConsumer>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoEventConsumerInfoRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IEventConsumerInfoRepository>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoContentRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoContentDatabaseRegistration)) |
|||
.As<IContentRepository>() |
|||
.As<IEventConsumer>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoRuleEventRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IRuleEventRepository>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoAppRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IAppRepository>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoSchemaRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<ISchemaRepository>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoAssetStatsRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IAssetStatsRepository>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoAssetRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IAssetRepository>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<MongoRuleRepository>() |
|||
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration)) |
|||
.As<IRuleRepository>() |
|||
.As<IEventConsumer>() |
|||
.As<IExternalSystem>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => |
|||
new CompoundEventConsumer( |
|||
c.Resolve<MongoAssetRepository>(), |
|||
c.Resolve<MongoAssetStatsRepository>())) |
|||
.As<IEventConsumer>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => |
|||
new CompoundEventConsumer( |
|||
c.Resolve<MongoSchemaRepository>(), |
|||
c.Resolve<CachingGraphQLService>(), |
|||
c.Resolve<CachingSchemaProvider>())) |
|||
.As<IEventConsumer>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register(c => |
|||
new CompoundEventConsumer( |
|||
c.Resolve<MongoAppRepository>(), |
|||
c.Resolve<CachingAppProvider>())) |
|||
.As<IEventConsumer>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// ==========================================================================
|
|||
// StoreServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using IdentityServer4.Stores; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps.Repositories; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Domain.Apps.Read.Assets.Repositories; |
|||
using Squidex.Domain.Apps.Read.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Read.History; |
|||
using Squidex.Domain.Apps.Read.History.Repositories; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Apps; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Assets; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Contents; |
|||
using Squidex.Domain.Apps.Read.MongoDb.History; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Rules; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Schemas; |
|||
using Squidex.Domain.Apps.Read.Rules.Repositories; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.Schemas.Repositories; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Domain.Users.MongoDb; |
|||
using Squidex.Domain.Users.MongoDb.Infrastructure; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.UsageTracking; |
|||
using Squidex.Shared.Users; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class StoreServices |
|||
{ |
|||
public static void AddMyStoreServices(this IServiceCollection services, IConfiguration config) |
|||
{ |
|||
config.ConfigureByOption("store:type", new Options |
|||
{ |
|||
["MongoDB"] = () => |
|||
{ |
|||
var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); |
|||
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); |
|||
var mongoContentDatabaseName = config.GetOptionalValue("store:mongoDb:contentDatabase", mongoDatabaseName); |
|||
|
|||
var mongoClient = Singletons<IMongoClient>.GetOrAdd(mongoConfiguration, s => new MongoClient(s)); |
|||
var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); |
|||
var mongoContentDatabase = mongoClient.GetDatabase(mongoContentDatabaseName); |
|||
|
|||
services.AddSingleton(c => new MongoUserStore(mongoDatabase)) |
|||
.As<IUserStore<IUser>>() |
|||
.As<IUserFactory>() |
|||
.As<IUserResolver>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoRoleStore(mongoDatabase)) |
|||
.As<IRoleStore<IRole>>() |
|||
.As<IRoleFactory>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoPersistedGrantStore(mongoDatabase)) |
|||
.As<IPersistedGrantStore>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoUsageStore(mongoDatabase)) |
|||
.As<IUsageStore>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoContentRepository(mongoContentDatabase, c.GetService<ISchemaProvider>())) |
|||
.As<IContentRepository>() |
|||
.As<IEventConsumer>(); |
|||
|
|||
services.AddSingleton(c => new MongoRuleEventRepository(mongoDatabase)) |
|||
.As<IRuleEventRepository>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoHistoryEventRepository(mongoDatabase, c.GetServices<IHistoryEventsCreator>())) |
|||
.As<IHistoryEventRepository>() |
|||
.As<IEventConsumer>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoAppRepository(mongoDatabase)) |
|||
.As<IAppRepository>() |
|||
.As<IAppEventConsumer>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoSchemaRepository(mongoDatabase, c.GetRequiredService<FieldRegistry>())) |
|||
.As<ISchemaRepository>() |
|||
.As<ISchemaEventConsumer>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoAssetStatsRepository(mongoDatabase)) |
|||
.As<IAssetStatsRepository>() |
|||
.As<IAssetEventConsumer>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoAssetRepository(mongoDatabase)) |
|||
.As<IAssetRepository>() |
|||
.As<IAssetEventConsumer>() |
|||
.As<IExternalSystem>(); |
|||
|
|||
services.AddSingleton(c => new MongoRuleRepository(mongoDatabase)) |
|||
.As<IRuleRepository>() |
|||
.As<IEventConsumer>() |
|||
.As<IExternalSystem>(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// SystemExtensions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class SystemExtensions |
|||
{ |
|||
public static void TestExternalSystems(this IServiceProvider services) |
|||
{ |
|||
var systems = services.GetRequiredService<IEnumerable<IExternalSystem>>(); |
|||
|
|||
foreach (var system in systems) |
|||
{ |
|||
system.Connect(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Usages.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Actors; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.CQRS.Events.Actors; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class Usages |
|||
{ |
|||
public static IApplicationBuilder UseMyEventStore(this IApplicationBuilder app) |
|||
{ |
|||
var services = app.ApplicationServices; |
|||
|
|||
services.GetService<EventConsumerCleaner>().CleanAsync().Wait(); |
|||
|
|||
var consumers = services.GetServices<IEventConsumer>(); |
|||
|
|||
foreach (var consumer in consumers) |
|||
{ |
|||
var actor = services.GetService<EventConsumerActor>(); |
|||
|
|||
if (actor != null) |
|||
{ |
|||
actor.SubscribeAsync(consumer); |
|||
|
|||
services.GetService<RemoteActors>().Connect(consumer.Name, actor); |
|||
} |
|||
} |
|||
|
|||
return app; |
|||
} |
|||
|
|||
public static IApplicationBuilder TestExternalSystems(this IApplicationBuilder app) |
|||
{ |
|||
var systems = app.ApplicationServices.GetRequiredService<IEnumerable<IExternalSystem>>(); |
|||
|
|||
foreach (var system in systems) |
|||
{ |
|||
system.Connect(); |
|||
} |
|||
|
|||
return app; |
|||
} |
|||
} |
|||
} |
|||
@ -1,113 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WriteModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Write.Apps; |
|||
using Squidex.Domain.Apps.Write.Assets; |
|||
using Squidex.Domain.Apps.Write.Contents; |
|||
using Squidex.Domain.Apps.Write.Rules; |
|||
using Squidex.Domain.Apps.Write.Schemas; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Pipeline.CommandMiddlewares; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public class WriteModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public WriteModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
builder.RegisterType<NoopUserEvents>() |
|||
.As<IUserEvents>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<JintScriptEngine>() |
|||
.As<IScriptEngine>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ContentVersionLoader>() |
|||
.As<IContentVersionLoader>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ETagCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EnrichWithTimestampCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EnrichWithActorCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EnrichWithAppIdCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<EnrichWithSchemaIdCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<AppCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<AssetCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<ContentCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<SchemaCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<RuleCommandMiddleware>() |
|||
.As<ICommandMiddleware>() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register<DomainObjectFactoryFunction<AppDomainObject>>(c => (id => new AppDomainObject(id, -1))) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register<DomainObjectFactoryFunction<AssetDomainObject>>(c => (id => new AssetDomainObject(id, -1))) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register<DomainObjectFactoryFunction<ContentDomainObject>>(c => (id => new ContentDomainObject(id, -1))) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register<DomainObjectFactoryFunction<RuleDomainObject>>(c => (id => new RuleDomainObject(id, -1))) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.Register<DomainObjectFactoryFunction<SchemaDomainObject>>(c => |
|||
{ |
|||
var fieldRegistry = c.Resolve<FieldRegistry>(); |
|||
|
|||
return id => new SchemaDomainObject(id, -1, fieldRegistry); |
|||
}) |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// ==========================================================================
|
|||
// WriteServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Write.Apps; |
|||
using Squidex.Domain.Apps.Write.Assets; |
|||
using Squidex.Domain.Apps.Write.Contents; |
|||
using Squidex.Domain.Apps.Write.Rules; |
|||
using Squidex.Domain.Apps.Write.Schemas; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Pipeline.CommandMiddlewares; |
|||
|
|||
namespace Squidex.Config.Domain |
|||
{ |
|||
public static class WriteServices |
|||
{ |
|||
public static void AddMyWriteServices(this IServiceCollection services) |
|||
{ |
|||
services.AddSingleton<NoopUserEvents>() |
|||
.As<IUserEvents>(); |
|||
|
|||
services.AddSingleton<JintScriptEngine>() |
|||
.As<IScriptEngine>(); |
|||
|
|||
services.AddSingleton<ContentVersionLoader>() |
|||
.As<IContentVersionLoader>(); |
|||
|
|||
services.AddSingleton<ETagCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<EnrichWithTimestampCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<EnrichWithActorCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<EnrichWithAppIdCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<EnrichWithSchemaIdCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<AppCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<AssetCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<ContentCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<SchemaCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<RuleCommandMiddleware>() |
|||
.As<ICommandMiddleware>(); |
|||
|
|||
services.AddSingleton<DomainObjectFactoryFunction<AppDomainObject>>(c => (id => new AppDomainObject(id, -1))); |
|||
services.AddSingleton<DomainObjectFactoryFunction<RuleDomainObject>>(c => (id => new RuleDomainObject(id, -1))); |
|||
services.AddSingleton<DomainObjectFactoryFunction<AssetDomainObject>>(c => (id => new AssetDomainObject(id, -1))); |
|||
services.AddSingleton<DomainObjectFactoryFunction<ContentDomainObject>>(c => (id => new ContentDomainObject(id, -1))); |
|||
|
|||
services.AddSingleton<DomainObjectFactoryFunction<SchemaDomainObject>>(c => |
|||
{ |
|||
var fieldRegistry = c.GetRequiredService<FieldRegistry>(); |
|||
|
|||
return id => new SchemaDomainObject(id, -1, fieldRegistry); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// ==========================================================================
|
|||
// IdentityServerServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Reflection; |
|||
using System.Security.Cryptography.X509Certificates; |
|||
using IdentityModel; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Stores; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Shared.Identity; |
|||
using Squidex.Shared.Users; |
|||
|
|||
namespace Squidex.Config.Identity |
|||
{ |
|||
public static class IdentityServerServices |
|||
{ |
|||
public static void AddMyIdentityServer(this IServiceCollection services) |
|||
{ |
|||
X509Certificate2 certificate; |
|||
|
|||
var assembly = typeof(IdentityServices).GetTypeInfo().Assembly; |
|||
|
|||
using (var certStream = assembly.GetManifestResourceStream("Squidex.Config.Identity.Cert.IdentityCert.pfx")) |
|||
{ |
|||
var certData = new byte[certStream.Length]; |
|||
|
|||
certStream.Read(certData, 0, certData.Length); |
|||
certificate = new X509Certificate2(certData, "password", |
|||
X509KeyStorageFlags.MachineKeySet | |
|||
X509KeyStorageFlags.PersistKeySet | |
|||
X509KeyStorageFlags.Exportable); |
|||
} |
|||
|
|||
services.AddSingleton( |
|||
GetApiResources()); |
|||
services.AddSingleton( |
|||
GetIdentityResources()); |
|||
services.AddSingleton<IUserClaimsPrincipalFactory<IUser>, |
|||
UserClaimsPrincipalFactoryWithEmail>(); |
|||
services.AddSingleton<IClientStore, |
|||
LazyClientStore>(); |
|||
services.AddSingleton<IResourceStore, |
|||
InMemoryResourcesStore>(); |
|||
|
|||
services.AddIdentityServer(options => |
|||
{ |
|||
options.UserInteraction.ErrorUrl = "/error/"; |
|||
}) |
|||
.AddAspNetIdentity<IUser>() |
|||
.AddInMemoryApiResources(GetApiResources()) |
|||
.AddInMemoryIdentityResources(GetIdentityResources()) |
|||
.AddSigningCredential(certificate); |
|||
} |
|||
|
|||
private static IEnumerable<ApiResource> GetApiResources() |
|||
{ |
|||
yield return new ApiResource(Constants.ApiScope) |
|||
{ |
|||
UserClaims = new List<string> |
|||
{ |
|||
JwtClaimTypes.Email, |
|||
JwtClaimTypes.Role |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private static IEnumerable<IdentityResource> GetIdentityResources() |
|||
{ |
|||
yield return new IdentityResources.OpenId(); |
|||
yield return new IdentityResources.Profile(); |
|||
yield return new IdentityResources.Email(); |
|||
yield return new IdentityResource(Constants.RoleScope, |
|||
new[] |
|||
{ |
|||
JwtClaimTypes.Role |
|||
}); |
|||
yield return new IdentityResource(Constants.ProfileScope, |
|||
new[] |
|||
{ |
|||
SquidexClaimTypes.SquidexDisplayName, |
|||
SquidexClaimTypes.SquidexPictureUrl |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +1,21 @@ |
|||
// ==========================================================================
|
|||
// ResetConsumerMessage.cs
|
|||
// Options.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Actors.Messages |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Config |
|||
{ |
|||
[TypeName(nameof(ResetConsumerMessage))] |
|||
public sealed class ResetConsumerMessage |
|||
public sealed class Options : Dictionary<string, Action> |
|||
{ |
|||
public Options() |
|||
: base(StringComparer.OrdinalIgnoreCase) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// ==========================================================================
|
|||
// ClientServices.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Orleans; |
|||
using Orleans.Runtime.Configuration; |
|||
using Squidex.Domain.Users.DataProtection.Orleans.Grains.Implementations; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.CQRS.Events.Orleans; |
|||
using Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public static class ClientServices |
|||
{ |
|||
public static void AddAppClient(this IServiceCollection services) |
|||
{ |
|||
services.AddSingleton<OrleansClientEventNotifier>() |
|||
.As<IEventNotifier>(); |
|||
|
|||
services.AddSingleton(c => |
|||
{ |
|||
var client = new ClientBuilder() |
|||
.UseConfiguration(ClientConfiguration.LocalhostSilo()) |
|||
.AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly) |
|||
.AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly) |
|||
.Build(); |
|||
|
|||
client.Connect().Wait(); |
|||
|
|||
return client; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public class IOrleansRunner |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// SiloExtensions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
using Orleans.Hosting; |
|||
|
|||
namespace Squidex.Config.Orleans |
|||
{ |
|||
public static class SiloExtensions |
|||
{ |
|||
public static ISiloHostBuilder UseContentRoot(this ISiloHostBuilder builder, string path) |
|||
{ |
|||
builder.ConfigureAppConfiguration(config => |
|||
{ |
|||
config.SetBasePath(path); |
|||
}); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
// ==========================================================================
|
|||
// ServiceExtensions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Config |
|||
{ |
|||
public static class ServiceExtensions |
|||
{ |
|||
public sealed class InterfaceRegistrator<T> |
|||
{ |
|||
private readonly IServiceCollection services; |
|||
|
|||
public InterfaceRegistrator(IServiceCollection services) |
|||
{ |
|||
this.services = services; |
|||
} |
|||
|
|||
public InterfaceRegistrator<T> As<TInterface>() |
|||
{ |
|||
if (typeof(TInterface) != typeof(T)) |
|||
{ |
|||
this.services.AddSingleton(typeof(TInterface), c => |
|||
{ |
|||
return c.GetRequiredService<T>(); |
|||
}); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
} |
|||
|
|||
public static InterfaceRegistrator<T> AddSingleton<T>(this IServiceCollection services, Func<IServiceProvider, T> factory) where T : class |
|||
{ |
|||
services.AddSingleton(typeof(T), factory); |
|||
|
|||
return new InterfaceRegistrator<T>(services); |
|||
} |
|||
|
|||
public static InterfaceRegistrator<T> AddSingleton<T>(this IServiceCollection services, T instance) where T : class |
|||
{ |
|||
services.AddSingleton(typeof(T), instance); |
|||
|
|||
return new InterfaceRegistrator<T>(services); |
|||
} |
|||
|
|||
public static InterfaceRegistrator<T> AddSingleton<T>(this IServiceCollection services) where T : class |
|||
{ |
|||
services.AddSingleton<T, T>(); |
|||
|
|||
return new InterfaceRegistrator<T>(services); |
|||
} |
|||
|
|||
public static T GetOptionalValue<T>(this IConfiguration config, string path, T defaultValue = default(T)) |
|||
{ |
|||
var value = config.GetValue<T>(path, defaultValue); |
|||
|
|||
return value; |
|||
} |
|||
|
|||
public static string GetRequiredValue(this IConfiguration config, string path) |
|||
{ |
|||
var value = config.GetValue<string>(path); |
|||
|
|||
if (string.IsNullOrWhiteSpace(value)) |
|||
{ |
|||
var name = string.Join(' ', path.Split(':').Select(x => x.ToPascalCase())); |
|||
|
|||
throw new ConfigurationException($"Configure the {name} with '{path}'."); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
public static string ConfigureByOption(this IConfiguration config, string path, Options options) |
|||
{ |
|||
var value = config.GetRequiredValue(path); |
|||
|
|||
if (options.TryGetValue(value, out var action)) |
|||
{ |
|||
action(); |
|||
} |
|||
else |
|||
{ |
|||
throw new ConfigurationException($"Unsupported value '{value}' for '{path}', supported: {string.Join(' ', options.Keys)}."); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WebModule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Autofac; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Config.Web |
|||
{ |
|||
public class WebModule : Module |
|||
{ |
|||
private IConfiguration Configuration { get; } |
|||
|
|||
public WebModule(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
builder.RegisterType<AppApiFilter>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
|
|||
builder.RegisterType<FileCallbackResultExecutor>() |
|||
.AsSelf() |
|||
.SingleInstance(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,77 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ActorRemoteTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FluentAssertions; |
|||
using Squidex.Infrastructure.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.Actors |
|||
{ |
|||
public class ActorRemoteTests |
|||
{ |
|||
[TypeName(nameof(SuccessMessage))] |
|||
public class SuccessMessage : object |
|||
{ |
|||
public int Counter { get; set; } |
|||
} |
|||
|
|||
private sealed class MyActor : IActor |
|||
{ |
|||
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(); |
|||
|
|||
public List<object> Invokes { get; } = new List<object>(); |
|||
|
|||
public Task StopAndWaitAsync() |
|||
{ |
|||
return dispatcher.StopAndWaitAsync(); |
|||
} |
|||
|
|||
public void Tell(object message) |
|||
{ |
|||
dispatcher.DispatchAsync(() => |
|||
{ |
|||
Invokes.Add(message); |
|||
}).Forget(); |
|||
} |
|||
} |
|||
|
|||
private readonly MyActor actor = new MyActor(); |
|||
private readonly TypeNameRegistry registry = new TypeNameRegistry(); |
|||
private readonly RemoteActors actors; |
|||
private readonly IActor remoteActor; |
|||
|
|||
public ActorRemoteTests() |
|||
{ |
|||
registry.Map(typeof(SuccessMessage)); |
|||
|
|||
actors = new RemoteActors(new DefaultRemoteActorChannel(new InMemoryPubSub(), registry)); |
|||
actors.Connect("my", actor); |
|||
|
|||
remoteActor = actors.Get("my"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_handle_messages_sequentially() |
|||
{ |
|||
remoteActor.Tell(new SuccessMessage { Counter = 1 }); |
|||
remoteActor.Tell(new SuccessMessage { Counter = 2 }); |
|||
remoteActor.Tell(new SuccessMessage { Counter = 3 }); |
|||
|
|||
await actor.StopAndWaitAsync(); |
|||
|
|||
actor.Invokes.ShouldBeEquivalentTo(new List<object> |
|||
{ |
|||
new SuccessMessage { Counter = 1 }, |
|||
new SuccessMessage { Counter = 2 }, |
|||
new SuccessMessage { Counter = 3 } |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,351 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerActorTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Squidex.Infrastructure.Actors; |
|||
using Squidex.Infrastructure.CQRS.Events.Actors.Messages; |
|||
using Squidex.Infrastructure.Log; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events.Actors |
|||
{ |
|||
public class EventConsumerActorTests |
|||
{ |
|||
public sealed class MyEvent : IEvent |
|||
{ |
|||
} |
|||
|
|||
private sealed class MyEventConsumerInfo : IEventConsumerInfo |
|||
{ |
|||
public bool IsStopped { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public string Error { get; set; } |
|||
|
|||
public string Position { get; set; } |
|||
} |
|||
|
|||
public sealed class MyEventConsumerActor : EventConsumerActor |
|||
{ |
|||
public MyEventConsumerActor( |
|||
EventDataFormatter formatter, |
|||
IEventStore eventStore, |
|||
IEventConsumerInfoRepository eventConsumerInfoRepository, |
|||
ISemanticLog log) |
|||
: base(formatter, eventStore, eventConsumerInfoRepository, log) |
|||
{ |
|||
} |
|||
|
|||
protected override IEventSubscription CreateSubscription(IEventStore eventStore, string streamFilter, string position) |
|||
{ |
|||
return eventStore.CreateSubscription(this, streamFilter, position); |
|||
} |
|||
} |
|||
|
|||
private readonly IEventConsumerInfoRepository eventConsumerInfoRepository = A.Fake<IEventConsumerInfoRepository>(); |
|||
private readonly IEventConsumer eventConsumer = A.Fake<IEventConsumer>(); |
|||
private readonly IEventStore eventStore = A.Fake<IEventStore>(); |
|||
private readonly IEventSubscription eventSubscription = A.Fake<IEventSubscription>(); |
|||
private readonly ISemanticLog log = A.Fake<ISemanticLog>(); |
|||
private readonly IActor sutActor; |
|||
private readonly IEventSubscriber sutSubscriber; |
|||
private readonly EventDataFormatter formatter = A.Fake<EventDataFormatter>(); |
|||
private readonly EventData eventData = new EventData(); |
|||
private readonly Envelope<IEvent> envelope = new Envelope<IEvent>(new MyEvent()); |
|||
private readonly EventConsumerActor sut; |
|||
private readonly MyEventConsumerInfo consumerInfo = new MyEventConsumerInfo(); |
|||
private readonly string consumerName; |
|||
|
|||
public EventConsumerActorTests() |
|||
{ |
|||
consumerInfo.Position = Guid.NewGuid().ToString(); |
|||
consumerName = eventConsumer.GetType().Name; |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, A<string>.Ignored)).Returns(eventSubscription); |
|||
|
|||
A.CallTo(() => eventConsumer.Name).Returns(consumerName); |
|||
A.CallTo(() => eventConsumerInfoRepository.FindAsync(consumerName)).Returns(consumerInfo); |
|||
|
|||
A.CallTo(() => formatter.Parse(eventData, true)).Returns(envelope); |
|||
|
|||
sut = new MyEventConsumerActor( |
|||
formatter, |
|||
eventStore, |
|||
eventConsumerInfoRepository, |
|||
log); |
|||
|
|||
sutActor = sut; |
|||
sutSubscriber = sut; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_subscribe_to_event_store_when_stopped_in_db() |
|||
{ |
|||
consumerInfo.IsStopped = true; |
|||
|
|||
await OnSubscribeAsync(); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, A<string>.Ignored)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_subscribe_to_event_store_when_not_found_in_db() |
|||
{ |
|||
A.CallTo(() => eventConsumerInfoRepository.FindAsync(consumerName)).Returns(Task.FromResult<IEventConsumerInfo>(null)); |
|||
|
|||
await OnSubscribeAsync(); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, A<string>.Ignored)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_subscribe_to_event_store_when_not_stopped_in_db() |
|||
{ |
|||
await OnSubscribeAsync(); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, A<string>.Ignored)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_stop_subscription_when_stopped() |
|||
{ |
|||
await OnSubscribeAsync(); |
|||
|
|||
sutActor.Tell(new StopConsumerMessage()); |
|||
sutActor.Tell(new StopConsumerMessage()); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, true, null)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventSubscription.StopAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_reset_consumer_when_resetting() |
|||
{ |
|||
await OnSubscribeAsync(); |
|||
|
|||
sutActor.Tell(new StopConsumerMessage()); |
|||
sutActor.Tell(new ResetConsumerMessage()); |
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, true, null)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, null, false, null)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventConsumer.ClearAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventSubscription.StopAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, consumerInfo.Position)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, null)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_invoke_and_update_position_when_event_received() |
|||
{ |
|||
var @event = new StoredEvent(Guid.NewGuid().ToString(), 123, eventData); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnEventAsync(eventSubscription, @event); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, @event.EventPosition, false, null)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_ignore_old_events() |
|||
{ |
|||
A.CallTo(() => formatter.Parse(eventData, true)) |
|||
.Throws(new TypeNameNotFoundException()); |
|||
|
|||
var @event = new StoredEvent(Guid.NewGuid().ToString(), 123, eventData); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnEventAsync(eventSubscription, @event); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, @event.EventPosition, false, null)) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_invoke_and_update_position_when_event_is_from_another_subscription() |
|||
{ |
|||
var @event = new StoredEvent(Guid.NewGuid().ToString(), 123, eventData); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnEventAsync(A.Fake<IEventSubscription>(), @event); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_make_error_handling_when_exception_is_from_another_subscription() |
|||
{ |
|||
var ex = new InvalidOperationException(); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnErrorAsync(A.Fake<IEventSubscription>(), ex); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, false, ex.ToString())) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_stop_if_resetting_failed() |
|||
{ |
|||
var ex = new InvalidOperationException(); |
|||
|
|||
A.CallTo(() => eventConsumer.ClearAsync()) |
|||
.Throws(ex); |
|||
|
|||
await OnSubscribeAsync(); |
|||
|
|||
sutActor.Tell(new ResetConsumerMessage()); |
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, true, ex.ToString())) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventSubscription.StopAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_stop_if_handling_failed() |
|||
{ |
|||
var ex = new InvalidOperationException(); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.Throws(ex); |
|||
|
|||
var @event = new StoredEvent(Guid.NewGuid().ToString(), 123, eventData); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnEventAsync(eventSubscription, @event); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, true, ex.ToString())) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventSubscription.StopAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_stop_if_deserialization_failed() |
|||
{ |
|||
var ex = new InvalidOperationException(); |
|||
|
|||
A.CallTo(() => formatter.Parse(eventData, true)) |
|||
.Throws(ex); |
|||
|
|||
var @event = new StoredEvent(Guid.NewGuid().ToString(), 123, eventData); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnEventAsync(eventSubscription, @event); |
|||
|
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.MustNotHaveHappened(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, true, ex.ToString())) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventSubscription.StopAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_start_after_stop_when_handling_failed() |
|||
{ |
|||
var exception = new InvalidOperationException(); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.Throws(exception); |
|||
|
|||
var @event = new StoredEvent(Guid.NewGuid().ToString(), 123, eventData); |
|||
|
|||
await OnSubscribeAsync(); |
|||
await OnEventAsync(eventSubscription, @event); |
|||
|
|||
sutActor.Tell(new StartConsumerMessage()); |
|||
sutActor.Tell(new StartConsumerMessage()); |
|||
sut.Dispose(); |
|||
|
|||
A.CallTo(() => eventConsumer.On(envelope)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => eventConsumerInfoRepository.SetAsync(consumerName, consumerInfo.Position, true, exception.ToString())) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventSubscription.StopAsync()) |
|||
.MustHaveHappened(Repeated.Exactly.Once); |
|||
|
|||
A.CallTo(() => eventStore.CreateSubscription(A<IEventSubscriber>.Ignored, A<string>.Ignored, A<string>.Ignored)) |
|||
.MustHaveHappened(Repeated.Exactly.Twice); |
|||
} |
|||
|
|||
private Task OnErrorAsync(IEventSubscription subscriber, Exception ex) |
|||
{ |
|||
return sutSubscriber.OnErrorAsync(subscriber, ex); |
|||
} |
|||
|
|||
private Task OnEventAsync(IEventSubscription subscriber, StoredEvent ev) |
|||
{ |
|||
return sutSubscriber.OnEventAsync(subscriber, ev); |
|||
} |
|||
|
|||
private Task OnSubscribeAsync() |
|||
{ |
|||
return sut.SubscribeAsync(eventConsumer); |
|||
} |
|||
} |
|||
} |
|||
@ -1,50 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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" }); |
|||
} |
|||
} |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EventConsumerCleanerTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using FakeItEasy; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Events |
|||
{ |
|||
public class EventConsumerCleanerTests |
|||
{ |
|||
[Fact] |
|||
public async Task Should_call_repository_with_all_names() |
|||
{ |
|||
var eventConsumer1 = A.Fake<IEventConsumer>(); |
|||
var eventConsumer2 = A.Fake<IEventConsumer>(); |
|||
|
|||
A.CallTo(() => eventConsumer1.Name).Returns("consumer1"); |
|||
A.CallTo(() => eventConsumer2.Name).Returns("consumer2"); |
|||
|
|||
var repository = A.Fake<IEventConsumerInfoRepository>(); |
|||
|
|||
var sut = new EventConsumerCleaner(new[] { eventConsumer1, eventConsumer2 }, repository); |
|||
|
|||
await sut.CleanAsync(); |
|||
|
|||
A.CallTo(() => repository.ClearAsync(A<IEnumerable<string>>.That.IsSameSequenceAs(new string[] { "consumer1", "consumer2" }))).MustHaveHappened(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue