diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs index 764150bbd..c3914af51 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs @@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services string? YearlyId { get; } + long BlockingApiCalls { get; } + long MaxApiCalls { get; } long MaxAssetSize { get; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs index 1548f3ea6..573ec971a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs @@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations public string? YearlyId { get; set; } + public long BlockingApiCalls { get; set; } + public long MaxApiCalls { get; set; } public long MaxAssetSize { get; set; } diff --git a/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs index f9dbd1f16..b2334f224 100644 --- a/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs +++ b/backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs @@ -25,16 +25,16 @@ namespace Squidex.Infrastructure.Assets.ImageSharp return Task.Run(() => { - var w = options.Width ?? 0; - var h = options.Height ?? 0; - - if (w <= 0 && h <= 0 && !options.Quality.HasValue) + if (!options.IsValid) { source.CopyTo(destination); return; } + var w = options.Width ?? 0; + var h = options.Height ?? 0; + using (var sourceImage = Image.Load(source, out var format)) { var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); diff --git a/backend/src/Squidex.Infrastructure/Assets/ResizeOptions.cs b/backend/src/Squidex.Infrastructure/Assets/ResizeOptions.cs index ea1cfde0d..b866003ec 100644 --- a/backend/src/Squidex.Infrastructure/Assets/ResizeOptions.cs +++ b/backend/src/Squidex.Infrastructure/Assets/ResizeOptions.cs @@ -11,6 +11,8 @@ namespace Squidex.Infrastructure.Assets { public sealed class ResizeOptions { + public ResizeMode Mode { get; set; } + public int? Width { get; set; } public int? Height { get; set; } @@ -21,7 +23,10 @@ namespace Squidex.Infrastructure.Assets public float? FocusY { get; set; } - public ResizeMode Mode { get; set; } + public bool IsValid + { + get { return Width > 0 || Height > 0 || Quality > 0; } + } public override string ToString() { diff --git a/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs b/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs index 6c96aa68c..cbe6cd070 100644 --- a/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs +++ b/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs @@ -73,7 +73,7 @@ namespace Squidex.Web.Pipeline var usage = await usageTracker.GetMonthlyCallsAsync(appId, DateTime.Today); - if (plan.MaxApiCalls >= 0 && usage > plan.MaxApiCalls * 1.1) + if (plan.BlockingApiCalls >= 0 && usage > plan.BlockingApiCalls) { context.Result = new StatusCodeResult(429); return; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index 7a9a54b5f..20be87168 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -138,7 +138,7 @@ namespace Squidex.Areas.Api.Controllers.Assets { var resizeOptions = query.ToResizeOptions(asset); - if (asset.Type == AssetType.Image && resizeOptions != null) + if (asset.Type == AssetType.Image && resizeOptions.IsValid) { var resizedAsset = $"{asset.Id}_{asset.FileVersion}_{resizeOptions}"; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetQuery.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetQuery.cs index 52e47534d..2a56f80f2 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetQuery.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetQuery.cs @@ -81,15 +81,10 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models [FromQuery(Name = "force")] public bool ForceResize { get; set; } - public ResizeOptions? ToResizeOptions(IAssetEntity asset) + public ResizeOptions ToResizeOptions(IAssetEntity asset) { Guard.NotNull(asset); - if (!Width.HasValue && !Height.HasValue && !Quality.HasValue) - { - return null; - } - var result = SimpleMapper.Map(this, new ResizeOptions()); var (x, y) = GetFocusPoint(asset); diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs index f1134dd2d..1223edf12 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs @@ -36,7 +36,7 @@ namespace Squidex.Web.Pipeline private readonly HttpContext httpContext = new DefaultHttpContext(); private readonly ActionExecutionDelegate next; private readonly ApiCostsFilter sut; - private long apiCallsMax; + private long apiCallsBlocking; private long apiCallsCurrent; private bool isNextCalled; @@ -57,8 +57,8 @@ namespace Squidex.Web.Pipeline A.CallTo(() => appPlansProvider.GetPlanForApp(appEntity)) .Returns((appPlan, "free")); - A.CallTo(() => appPlan.MaxApiCalls) - .ReturnsLazily(x => apiCallsMax); + A.CallTo(() => appPlan.BlockingApiCalls) + .ReturnsLazily(x => apiCallsBlocking); A.CallTo(() => usageTracker.GetMonthlyCallsAsync(A._, DateTime.Today)) .ReturnsLazily(x => Task.FromResult(apiCallsCurrent)); @@ -74,14 +74,14 @@ namespace Squidex.Web.Pipeline } [Fact] - public async Task Should_return_429_status_code_if_max_calls_over_limit() + public async Task Should_return_429_status_code_if_max_calls_over_blocking_limit() { sut.FilterDefinition = new ApiCostsAttribute(1); SetupApp(); apiCallsCurrent = 1000; - apiCallsMax = 600; + apiCallsBlocking = 600; await sut.OnActionExecutionAsync(actionContext, next); @@ -100,7 +100,7 @@ namespace Squidex.Web.Pipeline SetupApp(); apiCallsCurrent = 1000; - apiCallsMax = 1600; + apiCallsBlocking = 1600; await sut.OnActionExecutionAsync(actionContext, next); @@ -111,21 +111,21 @@ namespace Squidex.Web.Pipeline } [Fact] - public async Task Should_allow_small_buffer() + public async Task Should_not_allow_small_buffer() { sut.FilterDefinition = new ApiCostsAttribute(13); SetupApp(); apiCallsCurrent = 1099; - apiCallsMax = 1000; + apiCallsBlocking = 1000; await sut.OnActionExecutionAsync(actionContext, next); - Assert.True(isNextCalled); + Assert.False(isNextCalled); A.CallTo(() => usageTracker.TrackAsync(A._, A._, 13, A._)) - .MustHaveHappened(); + .MustNotHaveHappened(); } [Fact] @@ -136,7 +136,7 @@ namespace Squidex.Web.Pipeline SetupApp(); apiCallsCurrent = 1000; - apiCallsMax = 600; + apiCallsBlocking = 600; await sut.OnActionExecutionAsync(actionContext, next); @@ -152,7 +152,7 @@ namespace Squidex.Web.Pipeline sut.FilterDefinition = new ApiCostsAttribute(1); apiCallsCurrent = 1000; - apiCallsMax = 600; + apiCallsBlocking = 600; await sut.OnActionExecutionAsync(actionContext, next);