diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index 1b97323e7..e6a75104c 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/{app}/clients/")] [ProducesResponseType(typeof(ClientDto[]), 200)] - [ApiCosts(1)] + [ApiCosts(0)] public IActionResult GetClients(string app) { var response = App.Clients.Select(x => SimpleMapper.Map(x.Value, new ClientDto { Id = x.Key })).ToList(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index 79830a43b..6a84a1bbd 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -49,7 +49,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/{app}/contributors/")] [ProducesResponseType(typeof(ContributorsDto), 200)] - [ApiCosts(1)] + [ApiCosts(0)] public IActionResult GetContributors(string app) { var contributors = App.Contributors.Select(x => new ContributorDto { ContributorId = x.Key, Permission = x.Value }).ToArray(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index fc9562994..62ee48f89 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -49,6 +49,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/{app}/languages/")] [ProducesResponseType(typeof(AppLanguageDto[]), 200)] + [ApiCosts(0)] public IActionResult GetLanguages(string app) { var response = App.LanguagesConfig.OfType().Select(x => diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 5b8c2d6c5..62174f530 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -56,7 +56,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/")] [ProducesResponseType(typeof(AppDto[]), 200)] - [ApiCosts(1)] + [ApiCosts(0)] public async Task GetApps() { var subject = HttpContext.User.OpenIdSubject(); diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index e02d66871..5abc38ee4 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -60,6 +60,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [HttpGet] [Route("assets/{id}/")] [ProducesResponseType(200)] + [ApiCosts(0.5)] public async Task GetAssetContent(string app, Guid id, [FromQuery] int version = -1, [FromQuery] int? width = null, [FromQuery] int? height = null, [FromQuery] string mode = null) { var asset = await assetRepository.FindAssetAsync(id); diff --git a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index b362516fe..0932e7767 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -53,7 +53,7 @@ namespace Squidex.Areas.Api.Controllers.Plans [HttpGet] [Route("apps/{app}/plans/")] [ProducesResponseType(typeof(AppPlansDto), 200)] - [ApiCosts(0.5)] + [ApiCosts(0)] public IActionResult GetPlans(string app) { var planId = appPlansProvider.GetPlanForApp(App).Id; @@ -87,7 +87,7 @@ namespace Squidex.Areas.Api.Controllers.Plans [Route("apps/{app}/plan/")] [ProducesResponseType(typeof(PlanChangedDto), 200)] [ProducesResponseType(typeof(ErrorDto), 400)] - [ApiCosts(0.5)] + [ApiCosts(0)] public async Task ChangePlanAsync(string app, [FromBody] ChangePlanDto request) { var redirectUri = (string)null; diff --git a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs index d50ce6144..4d86a172b 100644 --- a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs +++ b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs @@ -22,15 +22,15 @@ namespace Squidex.Areas.IdentityServer.Config { public class LazyClientStore : IClientStore { - private readonly IAppProvider appState; + private readonly IAppProvider appProvider; private readonly Dictionary staticClients = new Dictionary(StringComparer.OrdinalIgnoreCase); - public LazyClientStore(IOptions urlsOptions, IAppProvider appState) + public LazyClientStore(IOptions urlsOptions, IAppProvider appProvider) { Guard.NotNull(urlsOptions, nameof(urlsOptions)); - Guard.NotNull(appState, nameof(appState)); + Guard.NotNull(appProvider, nameof(appProvider)); - this.appState = appState; + this.appProvider = appProvider; CreateStaticClients(urlsOptions); } @@ -51,7 +51,7 @@ namespace Squidex.Areas.IdentityServer.Config return null; } - var app = await appState.GetAppAsync(token[0]); + var app = await appProvider.GetAppAsync(token[0]); var appClient = app?.Clients.GetOrDefault(token[1]); diff --git a/src/Squidex/Config/Web/WebExtensions.cs b/src/Squidex/Config/Web/WebExtensions.cs index 70efe9aa1..aa6522278 100644 --- a/src/Squidex/Config/Web/WebExtensions.cs +++ b/src/Squidex/Config/Web/WebExtensions.cs @@ -17,7 +17,6 @@ namespace Squidex.Config.Web public static IApplicationBuilder UseMyTracking(this IApplicationBuilder app) { app.UseMiddleware(); - app.UseMiddleware(); return app; } diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 699a7d531..d52d8be76 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -16,8 +16,9 @@ namespace Squidex.Config.Web { public static void AddMyMvc(this IServiceCollection services) { - services.AddSingletonAs(); services.AddSingletonAs(); + services.AddSingletonAs(); + services.AddSingletonAs(); services.AddMvc().AddMySerializers(); services.AddCors(); diff --git a/src/Squidex/Pipeline/ApiCostsAttribute.cs b/src/Squidex/Pipeline/ApiCostsAttribute.cs index 2229a65cd..8231249c9 100644 --- a/src/Squidex/Pipeline/ApiCostsAttribute.cs +++ b/src/Squidex/Pipeline/ApiCostsAttribute.cs @@ -7,28 +7,19 @@ // ========================================================================== using System; -using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc; namespace Squidex.Pipeline { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public sealed class ApiCostsAttribute : ActionFilterAttribute + public sealed class ApiCostsAttribute : ServiceFilterAttribute { - private readonly double weight; - - private sealed class WeightFeature : IAppTrackingWeightFeature - { - public double Weight { get; set; } - } + public double Weight { get; } public ApiCostsAttribute(double weight) + : base(typeof(ApiCostsFilter)) { - this.weight = weight; - } - - public override void OnActionExecuting(ActionExecutingContext context) - { - context.HttpContext.Features.Set(new WeightFeature { Weight = weight }); + Weight = weight; } } } diff --git a/src/Squidex/Pipeline/ApiCostsFilter.cs b/src/Squidex/Pipeline/ApiCostsFilter.cs new file mode 100644 index 000000000..358c1a6f3 --- /dev/null +++ b/src/Squidex/Pipeline/ApiCostsFilter.cs @@ -0,0 +1,76 @@ +// ========================================================================== +// ApiCostsFilter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Squidex.Domain.Apps.Read.Apps.Services; +using Squidex.Infrastructure.UsageTracking; + +namespace Squidex.Pipeline +{ + public sealed class ApiCostsFilter : IAsyncActionFilter, IFilterContainer + { + private readonly IAppPlansProvider appPlanProvider; + private readonly IUsageTracker usageTracker; + + public ApiCostsFilter(IAppPlansProvider appPlanProvider, IUsageTracker usageTracker) + { + this.appPlanProvider = appPlanProvider; + + this.usageTracker = usageTracker; + } + + IFilterMetadata IFilterContainer.FilterDefinition { get; set; } + + public ApiCostsAttribute FilterDefinition + { + get + { + return (ApiCostsAttribute)((IFilterContainer)this).FilterDefinition; + } + } + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var appFeature = context.HttpContext.Features.Get(); + + if (appFeature?.App != null && FilterDefinition.Weight > 0) + { + var stopWatch = Stopwatch.StartNew(); + + try + { + var plan = appPlanProvider.GetPlanForApp(appFeature.App); + + var usage = await usageTracker.GetMonthlyCalls(appFeature.App.Id.ToString(), DateTime.Today); + + if (plan.MaxApiCalls >= 0 && (usage * 1.1) > plan.MaxApiCalls) + { + context.Result = new StatusCodeResult(429); + return; + } + + await next(); + } + finally + { + stopWatch.Stop(); + + await usageTracker.TrackAsync(appFeature.App.Id.ToString(), FilterDefinition.Weight, stopWatch.ElapsedMilliseconds); + } + } + else + { + await next(); + } + } + } +} diff --git a/src/Squidex/Pipeline/AppApiFilter.cs b/src/Squidex/Pipeline/AppApiFilter.cs index 8adc14336..a789893c7 100644 --- a/src/Squidex/Pipeline/AppApiFilter.cs +++ b/src/Squidex/Pipeline/AppApiFilter.cs @@ -6,27 +6,31 @@ // All rights reserved. // ========================================================================== -using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Infrastructure.UsageTracking; +using Squidex.Domain.Apps.Read; +using Squidex.Domain.Apps.Read.Apps; namespace Squidex.Pipeline { public sealed class AppApiFilter : IAsyncActionFilter { - private readonly Domain.Apps.Read.IAppProvider appState; - private readonly IAppPlansProvider appPlanProvider; - private readonly IUsageTracker usageTracker; + private readonly IAppProvider appProvider; - public AppApiFilter(Domain.Apps.Read.IAppProvider appState, IAppPlansProvider appPlanProvider, IUsageTracker usageTracker) + private sealed class AppFeature : IAppFeature { - this.appState = appState; - this.appPlanProvider = appPlanProvider; + public IAppEntity App { get; } - this.usageTracker = usageTracker; + public AppFeature(IAppEntity app) + { + App = app; + } + } + + public AppApiFilter(IAppProvider appProvider) + { + this.appProvider = appProvider; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) @@ -35,7 +39,7 @@ namespace Squidex.Pipeline if (!string.IsNullOrWhiteSpace(appName)) { - var app = await appState.GetAppAsync(appName); + var app = await appProvider.GetAppAsync(appName); if (app == null) { @@ -43,16 +47,6 @@ namespace Squidex.Pipeline return; } - var plan = appPlanProvider.GetPlanForApp(app); - - var usage = await usageTracker.GetMonthlyCalls(app.Id.ToString(), DateTime.Today); - - if (plan.MaxApiCalls >= 0 && (usage * 1.1) > plan.MaxApiCalls) - { - context.Result = new StatusCodeResult(429); - return; - } - context.HttpContext.Features.Set(new AppFeature(app)); } diff --git a/src/Squidex/Pipeline/AppFeature.cs b/src/Squidex/Pipeline/AppFeature.cs deleted file mode 100644 index 4f285c5b1..000000000 --- a/src/Squidex/Pipeline/AppFeature.cs +++ /dev/null @@ -1,22 +0,0 @@ -// ========================================================================== -// AppFeature.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Domain.Apps.Read.Apps; - -namespace Squidex.Pipeline -{ - public sealed class AppFeature : IAppFeature - { - public IAppEntity App { get; } - - public AppFeature(IAppEntity app) - { - App = app; - } - } -} \ No newline at end of file diff --git a/src/Squidex/Pipeline/AppTrackingMiddleware.cs b/src/Squidex/Pipeline/AppTrackingMiddleware.cs deleted file mode 100644 index f0e3e1bff..000000000 --- a/src/Squidex/Pipeline/AppTrackingMiddleware.cs +++ /dev/null @@ -1,45 +0,0 @@ -// ========================================================================== -// AppTrackingMiddleware.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Diagnostics; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Squidex.Infrastructure.UsageTracking; - -namespace Squidex.Pipeline -{ - public sealed class AppTrackingMiddleware - { - private readonly RequestDelegate next; - private readonly IUsageTracker usageTracker; - - public AppTrackingMiddleware(RequestDelegate next, IUsageTracker usageTracker) - { - this.next = next; - this.usageTracker = usageTracker; - } - - public async Task Invoke(HttpContext context) - { - var stopWatch = Stopwatch.StartNew(); - - await next(context); - - var appFeature = context.Features.Get(); - - if (appFeature?.App != null) - { - stopWatch.Stop(); - - var weight = context.Features.Get()?.Weight ?? 1; - - await usageTracker.TrackAsync(appFeature.App.Id.ToString(), weight, stopWatch.ElapsedMilliseconds); - } - } - } -} diff --git a/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs b/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs deleted file mode 100644 index 56274c735..000000000 --- a/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ========================================================================== -// IAppTrackingWeightFeature.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -namespace Squidex.Pipeline -{ - public interface IAppTrackingWeightFeature - { - double Weight { get; } - } -}