// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Squidex.Areas.Api.Controllers.Statistics.Models; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.UsageTracking; using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Statistics { /// /// Retrieves usage information for apps. /// [ApiExplorerSettings(GroupName = nameof(Statistics))] public sealed class UsagesController : ApiController { private readonly IApiUsageTracker usageTracker; private readonly IAppLogStore appLogStore; private readonly IAppPlansProvider appPlansProvider; private readonly IAssetUsageTracker assetStatsRepository; private readonly IDataProtector dataProtector; private readonly UrlsOptions urlsOptions; public UsagesController( ICommandBus commandBus, IApiUsageTracker usageTracker, IAppLogStore appLogStore, IAppPlansProvider appPlansProvider, IAssetUsageTracker assetStatsRepository, IDataProtectionProvider dataProtection, IOptions urlsOptions) : base(commandBus) { this.usageTracker = usageTracker; this.appLogStore = appLogStore; this.appPlansProvider = appPlansProvider; this.assetStatsRepository = assetStatsRepository; this.urlsOptions = urlsOptions.Value; dataProtector = dataProtection.CreateProtector("LogToken"); } /// /// Get api calls as log file. /// /// The name of the app. /// /// 200 => Usage tracking results returned. /// 404 => App not found. /// [HttpGet] [Route("apps/{app}/usages/log/")] [ProducesResponseType(typeof(LogDownloadDto), 200)] [ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiCosts(0)] public IActionResult GetLog(string app) { var token = dataProtector.Protect(App.Id.ToString()); var url = urlsOptions.BuildUrl($"/api/apps/log/{token}/"); var response = new LogDownloadDto { DownloadUrl = url }; return Ok(response); } /// /// Get api calls in date range. /// /// The name of the app. /// The from date. /// The to date. /// /// 200 => API call returned. /// 404 => App not found. /// 400 => Range between from date and to date is not valid or has more than 100 days. /// [HttpGet] [Route("apps/{app}/usages/calls/{fromDate}/{toDate}/")] [ProducesResponseType(typeof(CallsUsageDtoDto), 200)] [ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiCosts(0)] public async Task GetUsages(string app, DateTime fromDate, DateTime toDate) { if (fromDate > toDate && (toDate - fromDate).TotalDays > 100) { return BadRequest(); } var (summary, details) = await usageTracker.QueryAsync(AppId.ToString(), fromDate.Date, toDate.Date); var (plan, _) = appPlansProvider.GetPlanForApp(App); var response = CallsUsageDtoDto.FromStats(plan.MaxApiCalls, summary, details); return Ok(response); } /// /// Get total asset size. /// /// The name of the app. /// /// 200 => Storage usage returned. /// 404 => App not found. /// [HttpGet] [Route("apps/{app}/usages/storage/today/")] [ProducesResponseType(typeof(CurrentStorageDto), 200)] [ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiCosts(0)] public async Task GetCurrentStorageSize(string app) { var size = await assetStatsRepository.GetTotalSizeAsync(AppId); var (plan, _) = appPlansProvider.GetPlanForApp(App); var response = new CurrentStorageDto { Size = size, MaxAllowed = plan.MaxAssetSize }; return Ok(response); } /// /// Get asset usage by date. /// /// The name of the app. /// The from date. /// The to date. /// /// 200 => Storage usage returned. /// 400 => Range between from date and to date is not valid or has more than 100 days. /// 404 => App not found. /// [HttpGet] [Route("apps/{app}/usages/storage/{fromDate}/{toDate}/")] [ProducesResponseType(typeof(StorageUsagePerDateDto[]), 200)] [ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiCosts(0)] public async Task GetStorageSizes(string app, DateTime fromDate, DateTime toDate) { if (fromDate > toDate && (toDate - fromDate).TotalDays > 100) { return BadRequest(); } var usages = await assetStatsRepository.QueryAsync(AppId, fromDate.Date, toDate.Date); var models = usages.Select(StorageUsagePerDateDto.FromStats).ToArray(); return Ok(models); } [HttpGet] [Route("apps/log/{token}/")] [ApiExplorerSettings(IgnoreApi = true)] public IActionResult GetLogFile(string token) { var appId = dataProtector.Unprotect(token); var today = DateTime.UtcNow.Date; var fileName = $"Usage-{today:yyy-MM-dd}.csv"; var callback = new FileCallback((body, range, ct) => { return appLogStore.ReadLogAsync(Guid.Parse(appId), today.AddDays(-30), today, body, ct); }); return new FileCallbackResult("text/csv", callback) { FileDownloadName = fileName }; } } }