From 4870031c1a1c422fa8fbfbc5edee35898499a669 Mon Sep 17 00:00:00 2001 From: pavelbannov Date: Mon, 22 Aug 2022 14:00:57 +0300 Subject: [PATCH] Api: webhooks retry --- .../WebhooksGlobalFilterAttribute.cs | 2 +- common/ASC.Webhooks.Core/IWebhookPublisher.cs | 3 +- common/ASC.Webhooks.Core/WebhookPublisher.cs | 48 ++++++++----- .../Api/Settings/WebhooksController.cs | 67 +++++++++++++++---- .../RequestsDto/WebhookRetryRequestsDto.cs | 32 +++++++++ web/ASC.Web.Api/GlobalUsings.cs | 1 + 6 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 web/ASC.Web.Api/ApiModels/RequestsDto/WebhookRetryRequestsDto.cs diff --git a/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs b/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs index 737e53c117..6b03a2b28d 100644 --- a/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs +++ b/common/ASC.Api.Core/Middleware/WebhooksGlobalFilterAttribute.cs @@ -74,7 +74,7 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable var resultContent = Encoding.UTF8.GetString(_stream.ToArray()); - await _webhookPublisher.Publish(method, routePattern, resultContent); + await _webhookPublisher.PublishAsync(method, routePattern, resultContent); } catch (Exception e) { diff --git a/common/ASC.Webhooks.Core/IWebhookPublisher.cs b/common/ASC.Webhooks.Core/IWebhookPublisher.cs index b3a1ba9024..be00d4bd37 100644 --- a/common/ASC.Webhooks.Core/IWebhookPublisher.cs +++ b/common/ASC.Webhooks.Core/IWebhookPublisher.cs @@ -29,5 +29,6 @@ namespace ASC.Webhooks.Core; [Scope] public interface IWebhookPublisher { - public Task Publish(string method, string route, string requestPayload); + public Task PublishAsync(string method, string route, string requestPayload); + public Task PublishAsync(string method, string route, string requestPayload, int configId); } \ No newline at end of file diff --git a/common/ASC.Webhooks.Core/WebhookPublisher.cs b/common/ASC.Webhooks.Core/WebhookPublisher.cs index d969f1612c..f1d459b825 100644 --- a/common/ASC.Webhooks.Core/WebhookPublisher.cs +++ b/common/ASC.Webhooks.Core/WebhookPublisher.cs @@ -40,7 +40,7 @@ public class WebhookPublisher : IWebhookPublisher _webhookNotify = webhookNotify; } - public async Task Publish(string method, string route, string requestPayload) + public async Task PublishAsync(string method, string route, string requestPayload) { if (string.IsNullOrEmpty(requestPayload)) { @@ -51,23 +51,35 @@ public class WebhookPublisher : IWebhookPublisher await foreach (var config in webhookConfigs.Where(r => r.Enabled)) { - var webhooksLog = new WebhooksLog - { - Method = method, - Route = route, - CreationTime = DateTime.UtcNow, - RequestPayload = requestPayload, - ConfigId = config.Id - }; - - var webhook = await _dbWorker.WriteToJournal(webhooksLog); - - var request = new WebhookRequest - { - Id = webhook.Id - }; - - _webhookNotify.Publish(request, CacheNotifyAction.Update); + _ = await PublishAsync(method, route, requestPayload, config.Id); } } + + public async Task PublishAsync(string method, string route, string requestPayload, int configId) + { + if (string.IsNullOrEmpty(requestPayload)) + { + return null; + } + + var webhooksLog = new WebhooksLog + { + Method = method, + Route = route, + CreationTime = DateTime.UtcNow, + RequestPayload = requestPayload, + ConfigId = configId + }; + + var webhook = await _dbWorker.WriteToJournal(webhooksLog); + + var request = new WebhookRequest + { + Id = webhook.Id + }; + + _webhookNotify.Publish(request, CacheNotifyAction.Update); + + return webhook; + } } diff --git a/web/ASC.Web.Api/Api/Settings/WebhooksController.cs b/web/ASC.Web.Api/Api/Settings/WebhooksController.cs index 8b539c3ae2..d5ec7b3bdc 100644 --- a/web/ASC.Web.Api/Api/Settings/WebhooksController.cs +++ b/web/ASC.Web.Api/Api/Settings/WebhooksController.cs @@ -32,6 +32,7 @@ public class WebhooksController : BaseSettingsController private readonly PermissionContext _permissionContext; private readonly DbWorker _webhookDbWorker; private readonly IMapper _mapper; + private readonly WebhookPublisher _webhookPublisher; public WebhooksController( ApiContext context, @@ -41,18 +42,18 @@ public class WebhooksController : BaseSettingsController IMemoryCache memoryCache, DbWorker dbWorker, IHttpContextAccessor httpContextAccessor, - IMapper mapper) + IMapper mapper, + WebhookPublisher webhookPublisher) : base(apiContext, memoryCache, webItemManager, httpContextAccessor) { _context = context; _permissionContext = permissionContext; _webhookDbWorker = dbWorker; _mapper = mapper; + _webhookPublisher = webhookPublisher; } - /// - /// Read Webhooks history for actual tenant - /// - [HttpGet("webhooks")] + + [HttpGet("webhook")] public async IAsyncEnumerable GetTenantWebhooks() { _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); @@ -63,9 +64,6 @@ public class WebhooksController : BaseSettingsController } } - /// - /// Add new config for webhooks - /// [HttpPost("webhook")] public async Task CreateWebhook(WebhooksConfigRequestsDto model) { @@ -79,9 +77,6 @@ public class WebhooksController : BaseSettingsController return _mapper.Map(webhook); } - /// - /// Update config for webhooks - /// [HttpPut("webhook")] public async Task UpdateWebhook(WebhooksConfigRequestsDto model) { @@ -95,9 +90,6 @@ public class WebhooksController : BaseSettingsController return _mapper.Map(webhook); } - /// - /// Remove config for webhooks - /// [HttpDelete("webhook")] public async Task RemoveWebhook(int id) { @@ -119,5 +111,52 @@ public class WebhooksController : BaseSettingsController { yield return _mapper.Map(j); } + } + + [HttpPut("webhook/{id}/retry")] + public async Task RetryWebhook(int id) + { + _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); + + if (id == 0) + { + throw new ArgumentException(nameof(id)); + } + + var item = await _webhookDbWorker.ReadJournal(id); + + if (item == null) + { + throw new ItemNotFoundException(); + } + + if (item.Status >= 200 && item.Status <= 299 || item.Status == 0) + { + throw new HttpException(HttpStatusCode.Forbidden); + } + + var result = await _webhookPublisher.PublishAsync(item.Method, item.Route, item.RequestPayload, item.ConfigId); + + return _mapper.Map(result); + } + + [HttpPut("webhook/retry")] + public async IAsyncEnumerable RetryWebhooks(WebhookRetryRequestsDto model) + { + _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings); + + foreach (var id in model.Ids) + { + var item = await _webhookDbWorker.ReadJournal(id); + + if (item == null || item.Status >= 200 && item.Status <= 299 || item.Status == 0) + { + continue; + } + + var result = await _webhookPublisher.PublishAsync(item.Method, item.Route, item.RequestPayload, item.ConfigId); + + yield return _mapper.Map(result); + } } } \ No newline at end of file diff --git a/web/ASC.Web.Api/ApiModels/RequestsDto/WebhookRetryRequestsDto.cs b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhookRetryRequestsDto.cs new file mode 100644 index 0000000000..ea8591d9c3 --- /dev/null +++ b/web/ASC.Web.Api/ApiModels/RequestsDto/WebhookRetryRequestsDto.cs @@ -0,0 +1,32 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Web.Api.ApiModels.RequestsDto; + +public class WebhookRetryRequestsDto +{ + public List Ids { get; set; } +} diff --git a/web/ASC.Web.Api/GlobalUsings.cs b/web/ASC.Web.Api/GlobalUsings.cs index 4a0042ecaf..bdaae9b7bf 100644 --- a/web/ASC.Web.Api/GlobalUsings.cs +++ b/web/ASC.Web.Api/GlobalUsings.cs @@ -62,6 +62,7 @@ global using ASC.Common.Radicale.Core; global using ASC.Common.Security.Authorizing; global using ASC.Common.Threading; global using ASC.Common.Utils; +global using ASC.Common.Web; global using ASC.Core; global using ASC.Core.Billing; global using ASC.Core.Common.Configuration;