From 06fb27eeb5d28c4f146eb93c9c2559a134b86422 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 8 Apr 2017 19:17:21 +0200 Subject: [PATCH] Configuration improved --- .../RabbitMqEventConsumer.cs | 16 ++-- .../CQRS/Events/CompoundEventConsumer.cs | 4 +- .../CQRS/Events/EventReceiver.cs | 2 +- .../CQRS/Events/IEventConsumer.cs | 2 +- src/Squidex.Infrastructure/Singletons.cs | 23 +++++ .../Apps/MongoAppRepository_EventHandling.cs | 2 +- .../MongoContentRepository_EventHandling.cs | 2 +- .../History/MongoHistoryEventRepository.cs | 2 +- .../MongoSchemaRepository_EventHandling.cs | 2 +- .../Implementations/CachingAppProvider.cs | 2 +- .../Implementations/CachingSchemaProvider.cs | 2 +- src/Squidex/Config/Domain/ClusterModule.cs | 85 ------------------ .../Config/Domain/EventPublishersModule.cs | 80 +++++++++++++++++ src/Squidex/Config/Domain/EventStoreModule.cs | 53 +++++++----- .../Config/Domain/InfrastructureModule.cs | 10 +-- src/Squidex/Config/Domain/PubSubModule.cs | 70 +++++++++++++++ src/Squidex/Config/Domain/RabbitMqModule.cs | 48 ----------- src/Squidex/Config/Domain/StoreModule.cs | 6 +- .../Config/Domain/StoreMongoDbModule.cs | 51 +++++------ .../Config/Identity/IdentityServices.cs | 34 +++++--- src/Squidex/Config/MyUrlsOptions.cs | 6 ++ src/Squidex/Startup.cs | 10 +-- src/Squidex/appsettings.json | 86 ++++++++++--------- .../CQRS/Events/CompoundEventConsumerTests.cs | 4 +- 24 files changed, 342 insertions(+), 260 deletions(-) create mode 100644 src/Squidex.Infrastructure/Singletons.cs delete mode 100644 src/Squidex/Config/Domain/ClusterModule.cs create mode 100644 src/Squidex/Config/Domain/EventPublishersModule.cs create mode 100644 src/Squidex/Config/Domain/PubSubModule.cs delete mode 100644 src/Squidex/Config/Domain/RabbitMqModule.cs diff --git a/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs b/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs index efafc4645..b1b0c047b 100644 --- a/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs +++ b/src/Squidex.Infrastructure.RabbitMq/RabbitMqEventConsumer.cs @@ -21,25 +21,27 @@ namespace Squidex.Infrastructure.RabbitMq public sealed class RabbitMqEventConsumer : DisposableObjectBase, IExternalSystem, IEventConsumer { private readonly JsonSerializerSettings serializerSettings; + private readonly string eventPublisherName; private readonly string exchange; - private readonly string streamFilter; + private readonly string eventsFilter; private readonly ConnectionFactory connectionFactory; private readonly Lazy connection; private readonly Lazy channel; public string Name { - get { return GetType().Name; } + get { return eventPublisherName; } } - public string StreamFilter + public string EventsFilter { - get { return streamFilter; } + get { return eventsFilter; } } - public RabbitMqEventConsumer(JsonSerializerSettings serializerSettings, string uri, string exchange, string streamFilter) + public RabbitMqEventConsumer(JsonSerializerSettings serializerSettings, string eventPublisherName, string uri, string exchange, string eventsFilter) { Guard.NotNullOrEmpty(uri, nameof(uri)); + Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName)); Guard.NotNullOrEmpty(exchange, nameof(exchange)); Guard.NotNull(serializerSettings, nameof(serializerSettings)); @@ -49,8 +51,8 @@ namespace Squidex.Infrastructure.RabbitMq channel = new Lazy(() => connection.Value.CreateModel()); this.exchange = exchange; - - this.streamFilter = streamFilter; + this.eventsFilter = eventsFilter; + this.eventPublisherName = eventPublisherName; this.serializerSettings = serializerSettings; } diff --git a/src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs b/src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs index 11d044079..7a792650e 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/CompoundEventConsumer.cs @@ -17,9 +17,9 @@ namespace Squidex.Infrastructure.CQRS.Events public string Name { get; } - public string StreamFilter + public string EventsFilter { - get { return inners.FirstOrDefault()?.StreamFilter; } + get { return inners.FirstOrDefault()?.EventsFilter; } } public CompoundEventConsumer(IEventConsumer first, params IEventConsumer[] inners) diff --git a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs index 8ee8698e0..d7774e075 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs @@ -111,7 +111,7 @@ namespace Squidex.Infrastructure.CQRS.Events } await eventStore.GetEventsAsync(se => HandleEventAsync(eventConsumer, se, consumerName), ct, - eventConsumer.StreamFilter, lastHandledEventNumber); + eventConsumer.EventsFilter, lastHandledEventNumber); } catch (Exception ex) { diff --git a/src/Squidex.Infrastructure/CQRS/Events/IEventConsumer.cs b/src/Squidex.Infrastructure/CQRS/Events/IEventConsumer.cs index 936cd37cd..1cb4fba4d 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/IEventConsumer.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/IEventConsumer.cs @@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.CQRS.Events { string Name { get; } - string StreamFilter { get; } + string EventsFilter { get; } Task ClearAsync(); diff --git a/src/Squidex.Infrastructure/Singletons.cs b/src/Squidex.Infrastructure/Singletons.cs new file mode 100644 index 000000000..b9b6fe9f0 --- /dev/null +++ b/src/Squidex.Infrastructure/Singletons.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Singletons.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Concurrent; + +namespace Squidex.Infrastructure +{ + public static class Singletons + { + private static readonly ConcurrentDictionary instances = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public static T GetOrAdd(string key, Func factory) + { + return instances.GetOrAdd(key, factory); + } + } +} diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs index 5dfb421a8..96b6a6c41 100644 --- a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs @@ -23,7 +23,7 @@ namespace Squidex.Read.MongoDb.Apps get { return GetType().Name; } } - public string StreamFilter + public string EventsFilter { get { return "^app-"; } } diff --git a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs index 481f4a52e..784887ce7 100644 --- a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -37,7 +37,7 @@ namespace Squidex.Read.MongoDb.Contents get { return GetType().Name; } } - public string StreamFilter + public string EventsFilter { get { return "^content-"; } } diff --git a/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs b/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs index 5925db0ff..e05463748 100644 --- a/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs +++ b/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs @@ -32,7 +32,7 @@ namespace Squidex.Read.MongoDb.History get { return GetType().Name; } } - public string StreamFilter + public string EventsFilter { get { return "*"; } } diff --git a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs index d35a916fb..c6652b884 100644 --- a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs +++ b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs @@ -26,7 +26,7 @@ namespace Squidex.Read.MongoDb.Schemas get { return GetType().Name; } } - public string StreamFilter + public string EventsFilter { get { return "^schema-"; } } diff --git a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs index c5ec9c8cd..b983bd71a 100644 --- a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs +++ b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs @@ -32,7 +32,7 @@ namespace Squidex.Read.Apps.Services.Implementations get { return GetType().Name; } } - public string StreamFilter + public string EventsFilter { get { return "*"; } } diff --git a/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs b/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs index a667fe8cb..baba6aff1 100644 --- a/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs +++ b/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs @@ -32,7 +32,7 @@ namespace Squidex.Read.Schemas.Services.Implementations get { return GetType().Name; } } - public string StreamFilter + public string EventsFilter { get { return "*"; } } diff --git a/src/Squidex/Config/Domain/ClusterModule.cs b/src/Squidex/Config/Domain/ClusterModule.cs deleted file mode 100644 index 1e220f1d5..000000000 --- a/src/Squidex/Config/Domain/ClusterModule.cs +++ /dev/null @@ -1,85 +0,0 @@ -// ========================================================================== -// ClusterModule.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.CQRS.Events; -using Squidex.Infrastructure.Redis; -using StackExchange.Redis; - -namespace Squidex.Config.Domain -{ - public class ClusterModule : Module - { - private IConfiguration Configuration { get; } - - public ClusterModule(IConfiguration configuration) - { - Configuration = configuration; - } - - protected override void Load(ContainerBuilder builder) - { - var handleEvents = Configuration.GetValue("squidex:handleEvents"); - - if (handleEvents) - { - builder.RegisterType() - .AsSelf() - .InstancePerDependency(); - } - - var clustererType = Configuration.GetValue("squidex:clusterer:type"); - - if (string.IsNullOrWhiteSpace(clustererType)) - { - throw new ConfigurationException("You must specify the clusterer type in the 'squidex:clusterer:type' configuration section."); - } - - if (string.Equals(clustererType, "Redis", StringComparison.OrdinalIgnoreCase)) - { - var connectionString = Configuration.GetValue("squidex:clusterer:redis:connectionString"); - - if (string.IsNullOrWhiteSpace(connectionString)) - { - throw new ConfigurationException("You must specify the Redis connection string in the 'squidex:clusterer:redis:connectionString' configuration section."); - } - - try - { - var connectionMultiplexer = ConnectionMultiplexer.Connect(connectionString); - - builder.RegisterInstance(connectionMultiplexer) - .As() - .SingleInstance(); - } - catch (Exception ex) - { - throw new ConfigurationException($"Redis connection failed to connect to database {connectionString}", ex); - } - - builder.RegisterType() - .As() - .As() - .SingleInstance(); - } - else if (string.Equals(clustererType, "None", StringComparison.OrdinalIgnoreCase)) - { - builder.RegisterType() - .As() - .SingleInstance(); - } - else - { - throw new ConfigurationException($"Unsupported clusterer type '{clustererType}' for key 'squidex:clusterer:type', supported: Redis, None."); - } - } - } -} diff --git a/src/Squidex/Config/Domain/EventPublishersModule.cs b/src/Squidex/Config/Domain/EventPublishersModule.cs new file mode 100644 index 000000000..c30f762ca --- /dev/null +++ b/src/Squidex/Config/Domain/EventPublishersModule.cs @@ -0,0 +1,80 @@ +// ========================================================================== +// RabbitMqModule.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Autofac; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.RabbitMq; + +// ReSharper disable InvertIf + +namespace Squidex.Config.Domain +{ + public sealed class EventPublishersModule : Module + { + private IConfiguration Configuration { get; } + + public EventPublishersModule(IConfiguration configuration) + { + Configuration = configuration; + } + + protected override void Load(ContainerBuilder builder) + { + var eventPublishers = Configuration.GetSection("eventPublishers"); + + foreach (var child in eventPublishers.GetChildren()) + { + var eventPublisherType = child.GetValue("type"); + + if (string.IsNullOrWhiteSpace(eventPublisherType)) + { + throw new ConfigurationException($"Configure EventPublisher type with 'eventPublishers:{child.Key}:type'."); + } + + var eventsFilter = Configuration.GetValue("eventsFilter"); + + var enabled = child.GetValue("enabled"); + + if (string.Equals(eventPublisherType, "RabbitMq", StringComparison.OrdinalIgnoreCase)) + { + var configuration = child.GetValue("configuration"); + + if (string.IsNullOrWhiteSpace(configuration)) + { + throw new ConfigurationException($"Configure EventPublisher RabbitMq configuration with 'eventPublishers:{child.Key}:configuration'."); + } + + var exchange = child.GetValue("exchange"); + + if (string.IsNullOrWhiteSpace(exchange)) + { + throw new ConfigurationException($"Configure EventPublisher RabbitMq exchange with 'eventPublishers:{child.Key}:configuration'."); + } + + var name = $"EventPublishers_{child.Key}"; + + if (enabled) + { + builder.Register(c => new RabbitMqEventConsumer(c.Resolve(), name, configuration, exchange, eventsFilter)) + .As() + .As() + .SingleInstance(); + } + } + else + { + throw new ConfigurationException($"Unsupported value '{child.Key}' for 'eventPublishers:{child.Key}:type', supported: RabbitMq."); + } + } + } + } +} diff --git a/src/Squidex/Config/Domain/EventStoreModule.cs b/src/Squidex/Config/Domain/EventStoreModule.cs index ccc10e035..38f00ae1a 100644 --- a/src/Squidex/Config/Domain/EventStoreModule.cs +++ b/src/Squidex/Config/Domain/EventStoreModule.cs @@ -8,17 +8,20 @@ using System; using Autofac; +using Autofac.Core; using Microsoft.Extensions.Configuration; using MongoDB.Driver; -using NodaTime; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb.EventStore; namespace Squidex.Config.Domain { - public class EventStoreModule : Module + public sealed class EventStoreModule : Module { + private const string MongoClientRegistration = "EventStoreMongoClient"; + private const string MongoDatabaseRegistration = "EventStoreMongoDatabase"; + private IConfiguration Configuration { get; } public EventStoreModule(IConfiguration configuration) @@ -28,45 +31,55 @@ namespace Squidex.Config.Domain protected override void Load(ContainerBuilder builder) { - var storeType = Configuration.GetValue("squidex:eventStore:type"); + var consumeEvents = Configuration.GetValue("eventStore:consume"); - if (string.IsNullOrWhiteSpace(storeType)) + if (consumeEvents) { - throw new ConfigurationException("You must specify the store type in the 'squidex:eventStore:type' configuration section."); + builder.RegisterType() + .AsSelf() + .InstancePerDependency(); } - if (string.Equals(storeType, "MongoDb", StringComparison.OrdinalIgnoreCase)) + var eventStoreType = Configuration.GetValue("eventStore:type"); + + if (string.IsNullOrWhiteSpace(eventStoreType)) { - var databaseName = Configuration.GetValue("squidex:eventStore:mongoDb:databaseName"); + throw new ConfigurationException("Configure EventStore type with 'eventStore:type'."); + } - if (string.IsNullOrWhiteSpace(databaseName)) + if (string.Equals(eventStoreType, "MongoDb", StringComparison.OrdinalIgnoreCase)) + { + var configuration = Configuration.GetValue("eventStore:mongoDb:configuration"); + + if (string.IsNullOrWhiteSpace(configuration)) { - throw new ConfigurationException("You must specify the MongoDB database name in the 'squidex:eventStore:mongoDb:databaseName' configuration section."); + throw new ConfigurationException("Configure EventStore MongoDb configuration with 'eventStore:mongoDb:configuration'."); } - var connectionString = Configuration.GetValue("squidex:eventStore:mongoDb:connectionString"); + var database = Configuration.GetValue("eventStore:mongoDb:database"); - if (string.IsNullOrWhiteSpace(connectionString)) + if (string.IsNullOrWhiteSpace(database)) { - throw new ConfigurationException("You must specify the MongoDB connection string in the 'squidex:eventStore:mongoDb:connectionString' configuration section."); + throw new ConfigurationException("Configure EventStore MongoDb Database name with 'eventStore:mongoDb:database'."); } - builder.Register(c => - { - var mongoDbClient = new MongoClient(connectionString); - var mongoDatabase = mongoDbClient.GetDatabase(databaseName); + builder.Register(c => Singletons.GetOrAdd(configuration, s => new MongoClient(s))) + .Named(MongoClientRegistration) + .SingleInstance(); - var eventStore = new MongoEventStore(mongoDatabase, c.Resolve(), c.Resolve()); + builder.Register(c => c.ResolveNamed(MongoClientRegistration).GetDatabase(database)) + .Named(MongoDatabaseRegistration) + .SingleInstance(); - return eventStore; - }) + builder.RegisterType() + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() .As() .SingleInstance(); } else { - throw new ConfigurationException($"Unsupported store type '{storeType}' for key 'squidex:eventStore:type', supported: MongoDb."); + throw new ConfigurationException($"Unsupported value '{eventStoreType}' for 'eventStore:type', supported: MongoDb."); } } } diff --git a/src/Squidex/Config/Domain/InfrastructureModule.cs b/src/Squidex/Config/Domain/InfrastructureModule.cs index b84720bde..0c024ae3f 100644 --- a/src/Squidex/Config/Domain/InfrastructureModule.cs +++ b/src/Squidex/Config/Domain/InfrastructureModule.cs @@ -28,7 +28,7 @@ using Squidex.Pipeline; namespace Squidex.Config.Domain { - public class InfrastructureModule : Module + public sealed class InfrastructureModule : Module { private IConfiguration Configuration { get; } @@ -39,7 +39,7 @@ namespace Squidex.Config.Domain protected override void Load(ContainerBuilder builder) { - if (Configuration.GetValue("squidex:logging:human")) + if (Configuration.GetValue("logging:human")) { builder.Register(c => new Func(() => new JsonLogWriter(Formatting.Indented, true))) .AsSelf() @@ -52,11 +52,11 @@ namespace Squidex.Config.Domain .SingleInstance(); } - var logFile = Configuration.GetValue("squidex:logging:file"); + var loggingFile = Configuration.GetValue("logging:file"); - if (!string.IsNullOrWhiteSpace(logFile)) + if (!string.IsNullOrWhiteSpace(loggingFile)) { - builder.RegisterInstance(new FileChannel(logFile)) + builder.RegisterInstance(new FileChannel(loggingFile)) .As() .As() .SingleInstance(); diff --git a/src/Squidex/Config/Domain/PubSubModule.cs b/src/Squidex/Config/Domain/PubSubModule.cs new file mode 100644 index 000000000..7c406b05a --- /dev/null +++ b/src/Squidex/Config/Domain/PubSubModule.cs @@ -0,0 +1,70 @@ +// ========================================================================== +// ClusterModule.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 Squidex.Infrastructure.Redis; +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("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("pubsub:redis:configuration"); + + if (string.IsNullOrWhiteSpace(configuration)) + { + throw new ConfigurationException("Configure PubSub Redis configuration with pubSub:redis:configuration'."); + } + + builder.Register(c => Singletons.GetOrAdd(configuration, s => ConnectionMultiplexer.Connect(s))) + .Named(RedisRegistration) + .SingleInstance(); + + builder.RegisterType() + .WithParameter(ResolvedParameter.ForNamed(RedisRegistration)) + .As() + .As() + .SingleInstance(); + } + else if (string.Equals(pubSubType, "InMemory", StringComparison.OrdinalIgnoreCase)) + { + builder.RegisterType() + .As() + .SingleInstance(); + } + else + { + throw new ConfigurationException($"Unsupported value '{pubSubType}' for 'pubSub:type', supported: Redis, InMemory."); + } + } + } +} diff --git a/src/Squidex/Config/Domain/RabbitMqModule.cs b/src/Squidex/Config/Domain/RabbitMqModule.cs deleted file mode 100644 index f05c20124..000000000 --- a/src/Squidex/Config/Domain/RabbitMqModule.cs +++ /dev/null @@ -1,48 +0,0 @@ -// ========================================================================== -// RabbitMqModule.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Autofac; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.RabbitMq; - -// ReSharper disable InvertIf - -namespace Squidex.Config.Domain -{ - public sealed class RabbitMqModule : Module - { - private IConfiguration Configuration { get; } - - public RabbitMqModule(IConfiguration configuration) - { - Configuration = configuration; - } - - protected override void Load(ContainerBuilder builder) - { - var connectionString = Configuration.GetValue("squidex:eventPublishers:rabbitMq:connectionString"); - var exchange = Configuration.GetValue("squidex:eventPublishers:rabbitMq:exchange"); - var enabled = Configuration.GetValue("squidex:eventPublishers:rabbitMq:enabled"); - - if (!string.IsNullOrWhiteSpace(connectionString) && - !string.IsNullOrWhiteSpace(exchange) && - enabled) - { - var streamFilter = Configuration.GetValue("squidex:eventPublishers:rabbitMq:streamFilter"); - - builder.Register(c => new RabbitMqEventConsumer(c.Resolve(), connectionString, exchange, streamFilter)) - .As() - .As() - .SingleInstance(); - } - } - } -} diff --git a/src/Squidex/Config/Domain/StoreModule.cs b/src/Squidex/Config/Domain/StoreModule.cs index 194aff43a..85a17d57d 100644 --- a/src/Squidex/Config/Domain/StoreModule.cs +++ b/src/Squidex/Config/Domain/StoreModule.cs @@ -24,11 +24,11 @@ namespace Squidex.Config.Domain protected override void Load(ContainerBuilder builder) { - var storeType = Configuration.GetValue("squidex:stores:type"); + var storeType = Configuration.GetValue("store:type"); if (string.IsNullOrWhiteSpace(storeType)) { - throw new ConfigurationException("You must specify the store type in the 'squidex:stores:type' configuration section."); + throw new ConfigurationException("Configure the Store type with 'store:type'."); } if (string.Equals(storeType, "MongoDB", StringComparison.OrdinalIgnoreCase)) @@ -37,7 +37,7 @@ namespace Squidex.Config.Domain } else { - throw new ConfigurationException($"Unsupported store type '{storeType}' for key 'squidex:stores:type', supported: MongoDb."); + throw new ConfigurationException($"Unsupported value '{storeType}' for 'stores:type', supported: MongoDb."); } } } diff --git a/src/Squidex/Config/Domain/StoreMongoDbModule.cs b/src/Squidex/Config/Domain/StoreMongoDbModule.cs index 151ac5d9e..ed62559b1 100644 --- a/src/Squidex/Config/Domain/StoreMongoDbModule.cs +++ b/src/Squidex/Config/Domain/StoreMongoDbModule.cs @@ -34,8 +34,9 @@ namespace Squidex.Config.Domain { public class StoreMongoDbModule : Module { - private const string MongoDatabaseName = "MongoDatabaseName"; - private const string MongoDatabaseNameContent = "MongoDatabaseNameContent"; + private const string MongoClientRegistration = "StoreMongoClient"; + private const string MongoDatabaseRegistration = "StoreMongoDatabaseName"; + private const string MongoContentDatabaseRegistration = "StoreMongoDatabaseNameContent"; private IConfiguration Configuration { get; } @@ -46,42 +47,42 @@ namespace Squidex.Config.Domain protected override void Load(ContainerBuilder builder) { - var databaseName = Configuration.GetValue("squidex:stores:mongoDb:databaseName"); + var configuration = Configuration.GetValue("store:mongoDb:configuration"); - if (string.IsNullOrWhiteSpace(databaseName)) + if (string.IsNullOrWhiteSpace(configuration)) { - throw new ConfigurationException("You must specify the MongoDB database name in the 'squidex:stores:mongoDb:databaseName' configuration section."); + throw new ConfigurationException("Configure the Store MongoDb configuration with 'store:mongoDb:configuration'."); } - var connectionString = Configuration.GetValue("squidex:stores:mongoDb:connectionString"); + var database = Configuration.GetValue("store:mongoDb:database"); - if (string.IsNullOrWhiteSpace(connectionString)) + if (string.IsNullOrWhiteSpace(database)) { - throw new ConfigurationException("You must specify the MongoDB connection string in the 'squidex:stores:mongoDb:connectionString' configuration section."); + throw new ConfigurationException("Configure the Store MongoDb database with 'store:mongoDb:database'."); } - var databaseNameContent = Configuration.GetValue("squidex:stores:mongoDb:databaseNameContent"); + var contentDatabase = Configuration.GetValue("store:mongoDb:databaseNameContent"); - if (string.IsNullOrWhiteSpace(databaseNameContent)) + if (string.IsNullOrWhiteSpace(contentDatabase)) { - databaseNameContent = databaseName; + contentDatabase = database; } - builder.Register(c => new MongoClient(connectionString)) - .As() + builder.Register(c => Singletons.GetOrAdd(configuration, s => new MongoClient(s))) + .Named(MongoClientRegistration) .SingleInstance(); - builder.Register(c => c.Resolve().GetDatabase(databaseName)) - .Named(MongoDatabaseName) + builder.Register(c => c.ResolveNamed(MongoClientRegistration).GetDatabase(database)) + .Named(MongoDatabaseRegistration) .SingleInstance(); - builder.Register(c => c.Resolve().GetDatabase(databaseNameContent)) - .Named(MongoDatabaseNameContent) + builder.Register(c => c.ResolveNamed(MongoClientRegistration).GetDatabase(contentDatabase)) + .Named(MongoContentDatabaseRegistration) .SingleInstance(); builder.Register>(c => { - var usersCollection = c.ResolveNamed(MongoDatabaseName).GetCollection("Identity_Users"); + var usersCollection = c.ResolveNamed(MongoDatabaseRegistration).GetCollection("Identity_Users"); IndexChecks.EnsureUniqueIndexOnNormalizedEmail(usersCollection); IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection); @@ -92,7 +93,7 @@ namespace Squidex.Config.Domain builder.Register>(c => { - var rolesCollection = c.ResolveNamed(MongoDatabaseName).GetCollection("Identity_Roles"); + var rolesCollection = c.ResolveNamed(MongoDatabaseRegistration).GetCollection("Identity_Roles"); IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection); @@ -105,25 +106,25 @@ namespace Squidex.Config.Domain .InstancePerLifetimeScope(); builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() .SingleInstance(); builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() .AsSelf() .SingleInstance(); builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseNameContent)) + .WithParameter(ResolvedParameter.ForNamed(MongoContentDatabaseRegistration)) .As() .As() .AsSelf() .SingleInstance(); builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() .As() .As() @@ -131,14 +132,14 @@ namespace Squidex.Config.Domain .SingleInstance(); builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() .As() .AsSelf() .SingleInstance(); builder.RegisterType() - .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName)) + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() .As() .As() diff --git a/src/Squidex/Config/Identity/IdentityServices.cs b/src/Squidex/Config/Identity/IdentityServices.cs index 1333075b4..6fca53cb3 100644 --- a/src/Squidex/Config/Identity/IdentityServices.cs +++ b/src/Squidex/Config/Identity/IdentityServices.cs @@ -1,4 +1,5 @@ -// ========================================================================== + +// ========================================================================== // IdentityServices.cs // Squidex Headless CMS // ========================================================================== @@ -30,29 +31,40 @@ namespace Squidex.Config.Identity { var dataProtection = services.AddDataProtection().SetApplicationName("Squidex"); - var clustererType = configuration.GetValue("squidex:clusterer:type"); + var keyStoreType = configuration.GetValue("identity:keysStore:type"); + + if (string.IsNullOrWhiteSpace(keyStoreType)) + { + throw new ConfigurationException("Configure KeyStore type with 'identity:keysStore:type'."); + } - if (clustererType.Equals("redis", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(keyStoreType, "Redis", StringComparison.OrdinalIgnoreCase)) { - var connectionString = configuration.GetValue("squidex:clusterer:redis:connectionString"); + var redisConfiguration = configuration.GetValue("identity:keysStore:redis:configuration"); - if (string.IsNullOrWhiteSpace(connectionString)) + if (string.IsNullOrWhiteSpace(redisConfiguration)) { - throw new ConfigurationException("You must specify the Redis connection string in the 'squidex:clusterer:redis:connectionString' configuration section."); + throw new ConfigurationException("Configure KeyStore Redis configuration with 'identity:keysStore:redis:configuration'."); } - var connectionMultiplexer = ConnectionMultiplexer.Connect(connectionString); + var connectionMultiplexer = Singletons.GetOrAdd(redisConfiguration, s => ConnectionMultiplexer.Connect(s)); dataProtection.PersistKeysToRedis(connectionMultiplexer); } - else + else if (string.Equals(keyStoreType, "Folder", StringComparison.OrdinalIgnoreCase)) { - var keysFolder = configuration.GetValue("squidex:identity:keysFolder"); + var folderPath = configuration.GetValue("identity:keysStore:folder:path"); - if (!string.IsNullOrWhiteSpace(keysFolder)) + if (string.IsNullOrWhiteSpace(folderPath)) { - dataProtection.PersistKeysToFileSystem(new DirectoryInfo(keysFolder)); + throw new ConfigurationException("Configure KeyStore Folder path with 'identity:keysStore:folder:path'."); } + + dataProtection.PersistKeysToFileSystem(new DirectoryInfo(folderPath)); + } + else if (!string.Equals(keyStoreType, "InMemory", StringComparison.OrdinalIgnoreCase)) + { + throw new ConfigurationException($"Unsupported value '{keyStoreType}' for 'identity:keysStore:type', supported: Redis, Folder, InMemory."); } return services; diff --git a/src/Squidex/Config/MyUrlsOptions.cs b/src/Squidex/Config/MyUrlsOptions.cs index c2add641d..19b462f3c 100644 --- a/src/Squidex/Config/MyUrlsOptions.cs +++ b/src/Squidex/Config/MyUrlsOptions.cs @@ -7,6 +7,7 @@ // ========================================================================== using System; +using Squidex.Infrastructure; namespace Squidex.Config { @@ -18,6 +19,11 @@ namespace Squidex.Config public string BuildUrl(string path, bool trailingSlash = true) { + if (string.IsNullOrWhiteSpace(BaseUrl)) + { + throw new ConfigurationException("Configure BaseUrl with 'urls:baseUrl'."); + } + var url = $"{BaseUrl.TrimEnd('/')}/{path.Trim('/')}"; if (trailingSlash && diff --git a/src/Squidex/Startup.cs b/src/Squidex/Startup.cs index 88696ec2c..61caf9db5 100644 --- a/src/Squidex/Startup.cs +++ b/src/Squidex/Startup.cs @@ -51,7 +51,7 @@ namespace Squidex .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true) - .AddEnvironmentVariables(); + .AddEnvironmentVariables("SQUIDEX__"); Configuration = builder.Build(); } @@ -71,16 +71,16 @@ namespace Squidex services.AddRouting(); services.Configure( - Configuration.GetSection("squidex:urls")); + Configuration.GetSection("urls")); services.Configure( - Configuration.GetSection("squidex:identity")); + Configuration.GetSection("identity")); var builder = new ContainerBuilder(); builder.Populate(services); - builder.RegisterModule(new ClusterModule(Configuration)); + builder.RegisterModule(new EventPublishersModule(Configuration)); builder.RegisterModule(new EventStoreModule(Configuration)); builder.RegisterModule(new InfrastructureModule(Configuration)); - builder.RegisterModule(new RabbitMqModule(Configuration)); + builder.RegisterModule(new PubSubModule(Configuration)); builder.RegisterModule(new ReadModule(Configuration)); builder.RegisterModule(new StoreModule(Configuration)); builder.RegisterModule(new WebModule(Configuration)); diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 4a7c4d1aa..4f43afe7f 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -1,45 +1,53 @@ { - "squidex": { - "urls": { - "baseUrl": "http://localhost:5000" + "urls": { + "baseUrl": "http://localhost:5000" + }, + "logging": { + "human": false + }, + "pubSub": { + "type": "InMemory", + "redis": { + "configuration": "localhost:6379,resolveDns=1" + } + }, + "eventStore": { + "type": "MongoDb", + "mongoDb": { + "configuration": "mongodb://localhost", + "database": "Squidex" }, - "logging": { - "human": false - }, - "clusterer": { - "type": "none", + "consume": true + }, + "eventPublishers": { + "allToRabbitMq": { + "type": "RabbitMq", + "configuration": "amqp://guest:guest@localhost/", + "exchange": "squidex", + "enabled": false, + "eventsFilter": "*" + } + }, + "store": { + "type": "MongoDb", + "mongoDb": { + "configuration": "mongodb://localhost", + "contentDatabase": "SquidexContent", + "database": "Squidex" + } + }, + "identity": { + "googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com", + "googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg", + "lockAutomatically": true, + "keysStore": { + "type": "InMemory", "redis": { - "connectionString": "localhost:6379,resolveDns=1" - } - }, - "eventStore": { - "type": "mongoDb", - "mongoDb": { - "connectionString": "mongodb://localhost", - "databaseName": "Squidex" + "configuration": "localhost:6379,resolveDns=1" + }, + "folder": { + "path": "keys" } - }, - "eventPublishers": { - "rabbitMq": { - "connectionString": "amqp://guest:guest@localhost/", - "exchange": "squidex", - "enabled": false, - "streamFilter": "*" - } - }, - "stores": { - "type": "mongoDb", - "mongoDb": { - "connectionString": "mongodb://localhost", - "databaseName": "Squidex", - "databaseNameContent": "SquidexContent" - } - }, - "identity": { - "googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com", - "googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg", - "lockAutomatically": true - }, - "handleEvents": true + } } } \ No newline at end of file diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs index 7e49ac0fa..38ce91330 100644 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CQRS/Events/CompoundEventConsumerTests.cs @@ -47,11 +47,11 @@ namespace Squidex.Infrastructure.CQRS.Events { const string filter = "my-inner-filter"; - consumer1.Setup(x => x.StreamFilter).Returns(filter); + consumer1.Setup(x => x.EventsFilter).Returns(filter); var sut = new CompoundEventConsumer(consumer1.Object, consumer2.Object); - Assert.Equal(filter, sut.StreamFilter); + Assert.Equal(filter, sut.EventsFilter); } [Fact]