diff --git a/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs b/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs new file mode 100644 index 000000000..dcd1ca88b --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; + +namespace Squidex.Infrastructure.Commands +{ + public sealed class ReadonlyCommandMiddleware : ICommandMiddleware + { + private readonly IOptions options; + + public ReadonlyCommandMiddleware(IOptions options) + { + Guard.NotNull(options, nameof(options)); + + this.options = options; + } + + public Task HandleAsync(CommandContext context, Func next) + { + if (options.Value.IsReadonly) + { + throw new DomainException("Application is in readonly mode at the moment."); + } + + return next(); + } + } +} diff --git a/src/Squidex.Infrastructure/Commands/ReadonlyOptions.cs b/src/Squidex.Infrastructure/Commands/ReadonlyOptions.cs new file mode 100644 index 000000000..b5682c63d --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/ReadonlyOptions.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Commands +{ + public sealed class ReadonlyOptions + { + public bool IsReadonly { get; set; } + } +} diff --git a/src/Squidex/AppServices.cs b/src/Squidex/AppServices.cs index 3a6e0e91f..ff21cc224 100644 --- a/src/Squidex/AppServices.cs +++ b/src/Squidex/AppServices.cs @@ -13,6 +13,7 @@ using Squidex.Config; using Squidex.Config.Authentication; using Squidex.Config.Domain; using Squidex.Config.Web; +using Squidex.Infrastructure.Commands; namespace Squidex { @@ -40,6 +41,8 @@ namespace Squidex services.AddMySwaggerSettings(); services.AddMySubscriptionServices(config); + services.Configure( + config.GetSection("mode")); services.Configure( config.GetSection("urls")); services.Configure( diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 48804c635..96ee162e2 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -83,6 +83,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 7c1e57286..b72f5ec5c 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -1,4 +1,11 @@ { + "mode": { + /* + * Use this flag to set Squidex to readonly, e.g. when you deploy a second instance for migration. + */ + "isReadonly": false + }, + "urls": { /* * Set the base url of your application, to generate correct urls in background process. diff --git a/tests/Squidex.Infrastructure.Tests/Commands/ReadonlyCommandMiddlewareTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/ReadonlyCommandMiddlewareTests.cs new file mode 100644 index 000000000..ac098a25b --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Commands/ReadonlyCommandMiddlewareTests.cs @@ -0,0 +1,62 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using FakeItEasy; +using Microsoft.Extensions.Options; +using Squidex.Infrastructure.Tasks; +using Xunit; + +namespace Squidex.Infrastructure.Commands +{ + public class ReadonlyCommandMiddlewareTests + { + private readonly ICommand command = A.Dummy(); + private readonly ICommandBus commandBus = A.Dummy(); + private readonly ReadonlyOptions options = new ReadonlyOptions(); + private readonly ReadonlyCommandMiddleware sut; + + public ReadonlyCommandMiddlewareTests() + { + sut = new ReadonlyCommandMiddleware(Options.Create(options)); + } + + [Fact] + public async Task Should_throw_exception_if_readonly() + { + var context = new CommandContext(command, commandBus); + + options.IsReadonly = true; + + await Assert.ThrowsAsync(() => MakeCallAsync(context)); + + Assert.False(context.IsCompleted); + } + + [Fact] + public async Task Should_not_throw_exception_if_not_readonly() + { + var context = new CommandContext(command, commandBus); + + options.IsReadonly = false; + + await MakeCallAsync(context); + + Assert.True(context.IsCompleted); + } + + private async Task MakeCallAsync(CommandContext context) + { + await sut.HandleAsync(context, () => + { + context.Complete(true); + + return TaskHelper.Done; + }); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs b/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs index 5657141b1..705b398ca 100644 --- a/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs +++ b/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs @@ -17,6 +17,8 @@ namespace Squidex.Pipeline public class EnforceHttpsMiddlewareTests { private readonly RequestDelegate next; + private readonly MyUrlsOptions options = new MyUrlsOptions(); + private readonly EnforceHttpsMiddleware sut; private bool isNextCalled; public EnforceHttpsMiddlewareTests() @@ -27,6 +29,8 @@ namespace Squidex.Pipeline return TaskHelper.Done; }; + + sut = new EnforceHttpsMiddleware(Options.Create(options)); } [Fact] @@ -34,9 +38,9 @@ namespace Squidex.Pipeline { var httpContext = CreateHttpContext(); - var sut = new EnforceHttpsMiddleware(next, Options.Create(new MyUrlsOptions { EnforceHTTPS = true })); + options.EnforceHTTPS = true; - await sut.Invoke(httpContext); + await sut.InvokeAsync(httpContext, next); Assert.False(isNextCalled); Assert.Equal("https://squidex.local/path?query=1", httpContext.Response.Headers["Location"]); @@ -47,9 +51,9 @@ namespace Squidex.Pipeline { var httpContext = CreateHttpContext("https"); - var sut = new EnforceHttpsMiddleware(next, Options.Create(new MyUrlsOptions { EnforceHTTPS = true })); + options.EnforceHTTPS = true; - await sut.Invoke(httpContext); + await sut.InvokeAsync(httpContext, next); Assert.True(isNextCalled); Assert.Null((string)httpContext.Response.Headers["Location"]); @@ -60,9 +64,9 @@ namespace Squidex.Pipeline { var httpContext = CreateHttpContext("http"); - var sut = new EnforceHttpsMiddleware(next, Options.Create(new MyUrlsOptions { EnforceHTTPS = false })); + options.EnforceHTTPS = false; - await sut.Invoke(httpContext); + await sut.InvokeAsync(httpContext, next); Assert.True(isNextCalled); Assert.Null((string)httpContext.Response.Headers["Location"]);