Webhook: async

This commit is contained in:
pavelbannov 2022-08-17 12:18:50 +03:00
parent 6ec79e015e
commit 4cdce5b713
18 changed files with 312 additions and 161 deletions

View File

@ -24,8 +24,6 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace ASC.Api.Core.Middleware; namespace ASC.Api.Core.Middleware;
[Scope] [Scope]
@ -76,7 +74,7 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
var resultContent = Encoding.UTF8.GetString(_stream.ToArray()); var resultContent = Encoding.UTF8.GetString(_stream.ToArray());
_webhookPublisher.Publish(method, routePattern, JsonSerializer.Serialize(context.HttpContext.Request.Headers.ToDictionary(r => r.Key, v => v.Value)), resultContent); await _webhookPublisher.Publish(method, routePattern, resultContent);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -31,43 +31,37 @@ public class DbWorker
{ {
private readonly IDbContextFactory<WebhooksDbContext> _dbContextFactory; private readonly IDbContextFactory<WebhooksDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager; private readonly TenantManager _tenantManager;
public DbWorker(IDbContextFactory<WebhooksDbContext> dbContextFactory, TenantManager tenantManager) private readonly AuthContext _authContext;
private int Tenant
{
get
{
return _tenantManager.GetCurrentTenant().Id;
}
}
public DbWorker(IDbContextFactory<WebhooksDbContext> dbContextFactory, TenantManager tenantManager, AuthContext authContext)
{ {
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_tenantManager = tenantManager; _tenantManager = tenantManager;
} _authContext = authContext;
public void AddWebhookConfig(WebhooksConfig webhooksConfig)
{
webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id;
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var addObj = webhooksDbContext.WebhooksConfigs.Where(it =>
it.SecretKey == webhooksConfig.SecretKey &&
it.TenantId == webhooksConfig.TenantId &&
it.Uri == webhooksConfig.Uri).FirstOrDefault();
if (addObj != null)
{
return;
} }
webhooksDbContext.WebhooksConfigs.Add(webhooksConfig); public async Task AddWebhookConfig(string uri, string secretKey)
webhooksDbContext.SaveChanges();
}
public int ConfigsNumber()
{ {
using var webhooksDbContext = _dbContextFactory.CreateDbContext(); using var webhooksDbContext = _dbContextFactory.CreateDbContext();
return webhooksDbContext.WebhooksConfigs.Count();
await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey });
await webhooksDbContext.SaveChangesAsync();
} }
public List<WebhooksLog> GetTenantWebhooks() public async IAsyncEnumerable<WebhooksLog> GetTenantWebhooks()
{ {
var tenant = _tenantManager.GetCurrentTenant().Id;
using var webhooksDbContext = _dbContextFactory.CreateDbContext(); using var webhooksDbContext = _dbContextFactory.CreateDbContext();
return webhooksDbContext.WebhooksLogs.Where(it => it.TenantId == tenant)
var q = webhooksDbContext.WebhooksLogs
.Where(it => it.TenantId == Tenant)
.Select(t => new WebhooksLog .Select(t => new WebhooksLog
{ {
Uid = t.Uid, Uid = t.Uid,
@ -77,13 +71,61 @@ public class DbWorker
ResponsePayload = t.ResponsePayload, ResponsePayload = t.ResponsePayload,
ResponseHeaders = t.ResponseHeaders, ResponseHeaders = t.ResponseHeaders,
Status = t.Status Status = t.Status
}).ToList(); })
.AsAsyncEnumerable();
await foreach (var webhook in q)
{
yield return webhook;
}
} }
public List<WebhooksConfig> GetWebhookConfigs(int tenant) public IAsyncEnumerable<WebhooksConfig> GetWebhookConfigs()
{
var webhooksDbContext = _dbContextFactory.CreateDbContext();
return webhooksDbContext.WebhooksConfigs
.Where(t => t.TenantId == Tenant)
.AsAsyncEnumerable();
}
public async Task UpdateWebhookConfig(int id, string uri, string key)
{ {
using var webhooksDbContext = _dbContextFactory.CreateDbContext(); using var webhooksDbContext = _dbContextFactory.CreateDbContext();
return webhooksDbContext.WebhooksConfigs.Where(t => t.TenantId == tenant).ToList();
var updateObj = await webhooksDbContext.WebhooksConfigs
.Where(it => it.TenantId == Tenant && it.ConfigId == id)
.FirstOrDefaultAsync();
if (updateObj != null)
{
if (!string.IsNullOrEmpty(uri))
{
updateObj.Uri = uri;
}
if (!string.IsNullOrEmpty(key))
{
updateObj.SecretKey = key;
}
webhooksDbContext.WebhooksConfigs.Update(updateObj);
await webhooksDbContext.SaveChangesAsync();
}
}
public async Task RemoveWebhookConfig(int id)
{
var tenant = _tenantManager.GetCurrentTenant().Id;
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var removeObj = await webhooksDbContext.WebhooksConfigs
.Where(it => it.TenantId == tenant && it.ConfigId == id)
.FirstOrDefaultAsync();
webhooksDbContext.WebhooksConfigs.Remove(removeObj);
await webhooksDbContext.SaveChangesAsync();
} }
public WebhookEntry ReadFromJournal(int id) public WebhookEntry ReadFromJournal(int id)
@ -93,55 +135,34 @@ public class DbWorker
.Where(it => it.Id == id) .Where(it => it.Id == id)
.Join(webhooksDbContext.WebhooksConfigs, t => t.ConfigId, t => t.ConfigId, (payload, config) => new { payload, config }) .Join(webhooksDbContext.WebhooksConfigs, t => t.ConfigId, t => t.ConfigId, (payload, config) => new { payload, config })
.Select(t => new WebhookEntry { Id = t.payload.Id, Payload = t.payload.RequestPayload, SecretKey = t.config.SecretKey, Uri = t.config.Uri }) .Select(t => new WebhookEntry { Id = t.payload.Id, Payload = t.payload.RequestPayload, SecretKey = t.config.SecretKey, Uri = t.config.Uri })
.OrderBy(t => t.Id).FirstOrDefault(); .OrderBy(t => t.Id)
.FirstOrDefault();
} }
public void RemoveWebhookConfig(WebhooksConfig webhooksConfig) public async Task<int> WriteToJournal(WebhooksLog webhook)
{ {
webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id; webhook.TenantId = _tenantManager.GetCurrentTenant().Id;
webhook.Uid = _authContext.CurrentAccount.ID;
using var webhooksDbContext = _dbContextFactory.CreateDbContext(); using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var removeObj = webhooksDbContext.WebhooksConfigs.Where(it =>
it.SecretKey == webhooksConfig.SecretKey &&
it.TenantId == webhooksConfig.TenantId &&
it.Uri == webhooksConfig.Uri).FirstOrDefault();
webhooksDbContext.WebhooksConfigs.Remove(removeObj); var entity = await webhooksDbContext.WebhooksLogs.AddAsync(webhook);
webhooksDbContext.SaveChanges(); await webhooksDbContext.SaveChangesAsync();
return entity.Entity.Id;
} }
public void UpdateWebhookConfig(WebhooksConfig webhooksConfig) public async Task UpdateWebhookJournal(int id, ProcessStatus status, string requestHeaders, string responsePayload, string responseHeaders)
{
webhooksConfig.TenantId = _tenantManager.GetCurrentTenant().Id;
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var updateObj = webhooksDbContext.WebhooksConfigs.Where(it =>
it.SecretKey == webhooksConfig.SecretKey &&
it.TenantId == webhooksConfig.TenantId &&
it.Uri == webhooksConfig.Uri).FirstOrDefault();
webhooksDbContext.WebhooksConfigs.Update(updateObj);
webhooksDbContext.SaveChanges();
}
public async Task UpdateWebhookJournal(int id, ProcessStatus status, string responsePayload, string responseHeaders)
{ {
using var webhooksDbContext = _dbContextFactory.CreateDbContext(); using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webhook = await webhooksDbContext.WebhooksLogs.Where(t => t.Id == id).FirstOrDefaultAsync(); var webhook = await webhooksDbContext.WebhooksLogs.Where(t => t.Id == id).FirstOrDefaultAsync();
webhook.Status = status; webhook.Status = status;
webhook.RequestHeaders = requestHeaders;
webhook.ResponsePayload = responsePayload; webhook.ResponsePayload = responsePayload;
webhook.ResponseHeaders = responseHeaders; webhook.ResponseHeaders = responseHeaders;
webhooksDbContext.WebhooksLogs.Update(webhook); webhooksDbContext.WebhooksLogs.Update(webhook);
await webhooksDbContext.SaveChangesAsync(); await webhooksDbContext.SaveChangesAsync();
} }
public int WriteToJournal(WebhooksLog webhook)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var entity = webhooksDbContext.WebhooksLogs.Add(webhook);
webhooksDbContext.SaveChanges();
return entity.Entity.Id;
}
} }

View File

@ -25,12 +25,18 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Webhooks.Core.EF.Model; namespace ASC.Webhooks.Core.EF.Model;
public partial class WebhooksConfig
public class WebhooksConfig : BaseEntity
{ {
public int ConfigId { get; set; } public int ConfigId { get; set; }
public string SecretKey { get; set; } public string SecretKey { get; set; }
public int TenantId { get; set; } public int TenantId { get; set; }
public string Uri { get; set; } public string Uri { get; set; }
public override object[] GetKeys()
{
return new object[] { ConfigId };
}
} }
public static class WebhooksConfigExtension public static class WebhooksConfigExtension

View File

@ -39,7 +39,7 @@ public partial class WebhooksLog
public string ResponsePayload { get; set; } public string ResponsePayload { get; set; }
public ProcessStatus Status { get; set; } public ProcessStatus Status { get; set; }
public int TenantId { get; set; } public int TenantId { get; set; }
public string Uid { get; set; } public Guid Uid { get; set; }
} }
public static class WebhooksPayloadExtension public static class WebhooksPayloadExtension
@ -73,9 +73,10 @@ public static class WebhooksPayloadExtension
.HasColumnName("config_id"); .HasColumnName("config_id");
entity.Property(e => e.Uid) entity.Property(e => e.Uid)
.HasColumnType("varchar")
.HasColumnName("uid") .HasColumnName("uid")
.HasMaxLength(50); .HasColumnType("varchar(38)")
.HasCharSet("utf8")
.UseCollation("utf8_general_ci");
entity.Property(e => e.TenantId) entity.Property(e => e.TenantId)
.HasColumnName("tenant_id") .HasColumnName("tenant_id")

View File

@ -29,5 +29,5 @@ namespace ASC.Webhooks.Core;
[Scope] [Scope]
public interface IWebhookPublisher public interface IWebhookPublisher
{ {
public void Publish(string method, string route, string requestHeaders, string requestPayload); public Task Publish(string method, string route, string requestPayload);
} }

View File

@ -30,48 +30,38 @@ namespace ASC.Webhooks.Core;
public class WebhookPublisher : IWebhookPublisher public class WebhookPublisher : IWebhookPublisher
{ {
private readonly DbWorker _dbWorker; private readonly DbWorker _dbWorker;
private readonly TenantManager _tenantManager;
private readonly ICacheNotify<WebhookRequest> _webhookNotify; private readonly ICacheNotify<WebhookRequest> _webhookNotify;
private readonly AuthContext _authContext;
public WebhookPublisher( public WebhookPublisher(
DbWorker dbWorker, DbWorker dbWorker,
TenantManager tenantManager, ICacheNotify<WebhookRequest> webhookNotify)
ICacheNotify<WebhookRequest> webhookNotify,
AuthContext authContext)
{ {
_dbWorker = dbWorker; _dbWorker = dbWorker;
_tenantManager = tenantManager;
_webhookNotify = webhookNotify; _webhookNotify = webhookNotify;
_authContext = authContext;
} }
public void Publish(string method, string route, string requestHeaders, string requestPayload) public async Task Publish(string method, string route, string requestPayload)
{ {
if (string.IsNullOrEmpty(requestPayload)) if (string.IsNullOrEmpty(requestPayload))
{ {
return; return;
} }
var tenantId = _tenantManager.GetCurrentTenant().Id; var webhookConfigs = _dbWorker.GetWebhookConfigs();
var webhookConfigs = _dbWorker.GetWebhookConfigs(tenantId);
foreach (var config in webhookConfigs) await foreach (var config in webhookConfigs)
{ {
var webhooksLog = new WebhooksLog var webhooksLog = new WebhooksLog
{ {
Uid = _authContext.CurrentAccount.ID.ToString(),
TenantId = tenantId,
Method = method, Method = method,
Route = route, Route = route,
CreationTime = DateTime.UtcNow, CreationTime = DateTime.UtcNow,
RequestHeaders = requestHeaders,
RequestPayload = requestPayload, RequestPayload = requestPayload,
Status = ProcessStatus.InProcess, Status = ProcessStatus.InProcess,
ConfigId = config.ConfigId ConfigId = config.ConfigId
}; };
var id = _dbWorker.WriteToJournal(webhooksLog); var id = await _dbWorker.WriteToJournal(webhooksLog);
var request = new WebhookRequest var request = new WebhookRequest
{ {

View File

@ -72,7 +72,7 @@ public class WorkerService : BackgroundService
continue; continue;
} }
var tasks = new List<Task>(); var tasks = new List<Task>(queueSize);
var counter = 0; var counter = 0;
for (var i = 0; i < queueSize; i++) for (var i = 0; i < queueSize; i++)

View File

@ -37,7 +37,7 @@ public class Settings
{ {
var cfg = configuration.GetSetting<Settings>("webhooks"); var cfg = configuration.GetSetting<Settings>("webhooks");
RepeatCount = cfg.RepeatCount ?? 5; RepeatCount = cfg.RepeatCount ?? 5;
ThreadCount = cfg.ThreadCount ?? 1; ThreadCount = cfg.ThreadCount ?? 10;
} }
public int? RepeatCount { get; } public int? RepeatCount { get; }
public int? ThreadCount { get; } public int? ThreadCount { get; }

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // 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 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Text.Json.Serialization;
namespace ASC.Webhooks.Service; namespace ASC.Webhooks.Service;
[Singletone] [Singletone]
@ -32,12 +34,19 @@ public class WebhookSender
private readonly IHttpClientFactory _clientFactory; private readonly IHttpClientFactory _clientFactory;
private readonly ILogger _log; private readonly ILogger _log;
private readonly IServiceScopeFactory _scopeFactory; private readonly IServiceScopeFactory _scopeFactory;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory) public WebhookSender(ILoggerProvider options, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory)
{ {
_log = options.CreateLogger("ASC.Webhooks.Core"); _log = options.CreateLogger("ASC.Webhooks.Core");
_scopeFactory = scopeFactory; _scopeFactory = scopeFactory;
_clientFactory = clientFactory; _clientFactory = clientFactory;
_jsonSerializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
IgnoreReadOnlyProperties = true,
WriteIndented = true
};
} }
public async Task Send(WebhookRequest webhookRequest, CancellationToken cancellationToken) public async Task Send(WebhookRequest webhookRequest, CancellationToken cancellationToken)
@ -51,6 +60,7 @@ public class WebhookSender
var status = ProcessStatus.InProcess; var status = ProcessStatus.InProcess;
string responsePayload = null; string responsePayload = null;
string responseHeaders = null; string responseHeaders = null;
string requestHeaders = null;
try try
{ {
@ -62,10 +72,12 @@ public class WebhookSender
request.Headers.Add("Accept", "*/*"); request.Headers.Add("Accept", "*/*");
request.Headers.Add("Secret", "SHA256=" + GetSecretHash(entry.SecretKey, data)); request.Headers.Add("Secret", "SHA256=" + GetSecretHash(entry.SecretKey, data));
requestHeaders = JsonSerializer.Serialize(request.Headers.ToDictionary(r => r.Key, v => v.Value), _jsonSerializerOptions);
var response = await httpClient.SendAsync(request, cancellationToken); var response = await httpClient.SendAsync(request, cancellationToken);
status = ProcessStatus.Success; status = ProcessStatus.Success;
responseHeaders = JsonSerializer.Serialize(response.Headers.ToDictionary(r => r.Key, v => v.Value)); responseHeaders = JsonSerializer.Serialize(response.Headers.ToDictionary(r => r.Key, v => v.Value), _jsonSerializerOptions);
responsePayload = await response.Content.ReadAsStringAsync(); responsePayload = await response.Content.ReadAsStringAsync();
_log.DebugResponse(response); _log.DebugResponse(response);
@ -82,7 +94,7 @@ public class WebhookSender
_log.ErrorWithException(e); _log.ErrorWithException(e);
} }
await dbWorker.UpdateWebhookJournal(entry.Id, status, responsePayload, responseHeaders); await dbWorker.UpdateWebhookJournal(entry.Id, status, requestHeaders, responsePayload, responseHeaders);
} }
private string GetSecretHash(string secretKey, string body) private string GetSecretHash(string secretKey, string body)

View File

@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace ASC.Migrations.MySql.Migrations.WebhooksDb namespace ASC.Migrations.MySql.Migrations.WebhooksDb
{ {
[DbContext(typeof(WebhooksDbContext))] [DbContext(typeof(WebhooksDbContext))]
[Migration("20220816111541_WebhooksDbContext_Upgrade1")] [Migration("20220816154718_WebhooksDbContext_Upgrade1")]
partial class WebhooksDbContext_Upgrade1 partial class WebhooksDbContext_Upgrade1
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -111,9 +111,11 @@ namespace ASC.Migrations.MySql.Migrations.WebhooksDb
.HasColumnName("tenant_id"); .HasColumnName("tenant_id");
b.Property<string>("Uid") b.Property<string>("Uid")
.HasMaxLength(50) .IsRequired()
.HasColumnType("varchar(50)") .HasColumnType("varchar(36)")
.HasColumnName("uid"); .HasColumnName("uid")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.HasKey("Id") b.HasKey("Id")
.HasName("PRIMARY"); .HasName("PRIMARY");

View File

@ -13,6 +13,26 @@ namespace ASC.Migrations.MySql.Migrations.WebhooksDb
table: "webhooks_logs", table: "webhooks_logs",
newName: "route"); newName: "route");
migrationBuilder.UpdateData(
table: "webhooks_logs",
keyColumn: "uid",
keyValue: null,
column: "uid",
value: "");
migrationBuilder.AlterColumn<string>(
name: "uid",
table: "webhooks_logs",
type: "varchar(36)",
nullable: false,
collation: "utf8_general_ci",
oldClrType: typeof(string),
oldType: "varchar(50)",
oldMaxLength: 50,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "response_payload", name: "response_payload",
table: "webhooks_logs", table: "webhooks_logs",
@ -56,6 +76,18 @@ namespace ASC.Migrations.MySql.Migrations.WebhooksDb
table: "webhooks_logs", table: "webhooks_logs",
newName: "event"); newName: "event");
migrationBuilder.AlterColumn<string>(
name: "uid",
table: "webhooks_logs",
type: "varchar(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(36)",
oldCollation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8")
.OldAnnotation("MySql:CharSet", "utf8");
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "response_payload", name: "response_payload",
table: "webhooks_logs", table: "webhooks_logs",

View File

@ -109,9 +109,11 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnName("tenant_id"); .HasColumnName("tenant_id");
b.Property<string>("Uid") b.Property<string>("Uid")
.HasMaxLength(50) .IsRequired()
.HasColumnType("varchar(50)") .HasColumnType("varchar(36)")
.HasColumnName("uid"); .HasColumnName("uid")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.HasKey("Id") b.HasKey("Id")
.HasName("PRIMARY"); .HasName("PRIMARY");

View File

@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
{ {
[DbContext(typeof(WebhooksDbContext))] [DbContext(typeof(WebhooksDbContext))]
[Migration("20220816111541_WebhooksDbContext_Upgrade1")] [Migration("20220816154718_WebhooksDbContext_Upgrade1")]
partial class WebhooksDbContext_Upgrade1 partial class WebhooksDbContext_Upgrade1
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -109,6 +109,7 @@ namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
.HasColumnName("tenant_id"); .HasColumnName("tenant_id");
b.Property<string>("Uid") b.Property<string>("Uid")
.IsRequired()
.HasMaxLength(50) .HasMaxLength(50)
.HasColumnType("varchar") .HasColumnType("varchar")
.HasColumnName("uid"); .HasColumnName("uid");

View File

@ -13,6 +13,18 @@ namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
table: "webhooks_logs", table: "webhooks_logs",
newName: "route"); newName: "route");
migrationBuilder.AlterColumn<string>(
name: "uid",
table: "webhooks_logs",
type: "varchar",
maxLength: 50,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "varchar",
oldMaxLength: 50,
oldNullable: true);
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "response_payload", name: "response_payload",
table: "webhooks_logs", table: "webhooks_logs",
@ -49,6 +61,16 @@ namespace ASC.Migrations.PostgreSql.Migrations.WebhooksDb
table: "webhooks_logs", table: "webhooks_logs",
newName: "event"); newName: "event");
migrationBuilder.AlterColumn<string>(
name: "uid",
table: "webhooks_logs",
type: "varchar",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar",
oldMaxLength: 50);
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "response_payload", name: "response_payload",
table: "webhooks_logs", table: "webhooks_logs",

View File

@ -107,6 +107,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnName("tenant_id"); .HasColumnName("tenant_id");
b.Property<string>("Uid") b.Property<string>("Uid")
.IsRequired()
.HasMaxLength(50) .HasMaxLength(50)
.HasColumnType("varchar") .HasColumnType("varchar")
.HasColumnName("uid"); .HasColumnName("uid");

View File

@ -28,15 +28,18 @@ namespace ASC.Web.Api.Controllers.Settings;
public class WebhooksController : BaseSettingsController public class WebhooksController : BaseSettingsController
{ {
private readonly PermissionContext _permissionContext;
private readonly DbWorker _webhookDbWorker; private readonly DbWorker _webhookDbWorker;
public WebhooksController( public WebhooksController(
PermissionContext permissionContext,
ApiContext apiContext, ApiContext apiContext,
WebItemManager webItemManager, WebItemManager webItemManager,
IMemoryCache memoryCache, IMemoryCache memoryCache,
DbWorker dbWorker, DbWorker dbWorker,
IHttpContextAccessor httpContextAccessor) : base(apiContext, memoryCache, webItemManager, httpContextAccessor) IHttpContextAccessor httpContextAccessor) : base(apiContext, memoryCache, webItemManager, httpContextAccessor)
{ {
_permissionContext = permissionContext;
_webhookDbWorker = dbWorker; _webhookDbWorker = dbWorker;
} }
@ -44,65 +47,59 @@ public class WebhooksController : BaseSettingsController
/// Add new config for webhooks /// Add new config for webhooks
/// </summary> /// </summary>
[HttpPost("webhook")] [HttpPost("webhook")]
public void CreateWebhook(WebhooksConfig model) public async Task<WebhooksConfigDto> CreateWebhook(WebhooksConfigRequestsDto model)
{ {
if (model.Uri == null) _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
{
throw new ArgumentNullException("Uri");
}
if (model.SecretKey == null) ArgumentNullException.ThrowIfNull(model.Uri);
{ ArgumentNullException.ThrowIfNull(model.SecretKey);
throw new ArgumentNullException("SecretKey");
}
_webhookDbWorker.AddWebhookConfig(model); await _webhookDbWorker.AddWebhookConfig(model.Uri, model.SecretKey);
return new WebhooksConfigDto
{
Uri = model.Uri
};
} }
/// <summary> /// <summary>
/// Update config for webhooks /// Update config for webhooks
/// </summary> /// </summary>
[HttpPut("webhook")] [HttpPut("webhook")]
public void UpdateWebhook(WebhooksConfig model) public async Task<WebhooksConfigDto> UpdateWebhook(WebhooksConfigRequestsDto model)
{ {
if (model.Uri == null) _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
{
throw new ArgumentNullException("Uri");
}
if (model.SecretKey == null) ArgumentNullException.ThrowIfNull(model.Uri);
{ ArgumentNullException.ThrowIfNull(model.SecretKey);
throw new ArgumentNullException("SecretKey");
}
_webhookDbWorker.UpdateWebhookConfig(model); await _webhookDbWorker.UpdateWebhookConfig(model.Id, model.Uri, model.SecretKey);
return new WebhooksConfigDto
{
Uri = model.Uri
};
} }
/// <summary> /// <summary>
/// Remove config for webhooks /// Remove config for webhooks
/// </summary> /// </summary>
[HttpDelete("webhook")] [HttpDelete("webhook")]
public void RemoveWebhook(WebhooksConfig model) public async Task RemoveWebhook(int id)
{ {
if (model.Uri == null) _permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
{
throw new ArgumentNullException("Uri");
}
if (model.SecretKey == null) await _webhookDbWorker.RemoveWebhookConfig(id);
{
throw new ArgumentNullException("SecretKey");
}
_webhookDbWorker.RemoveWebhookConfig(model);
} }
/// <summary> /// <summary>
/// Read Webhooks history for actual tenant /// Read Webhooks history for actual tenant
/// </summary> /// </summary>
[HttpGet("webhooks")] [HttpGet("webhooks")]
public List<WebhooksLog> TenantWebhooks() public IAsyncEnumerable<WebhooksLog> TenantWebhooks()
{ {
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return _webhookDbWorker.GetTenantWebhooks(); return _webhookDbWorker.GetTenantWebhooks();
} }
} }

View File

@ -0,0 +1,34 @@
// (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 WebhooksConfigRequestsDto
{
public int Id { get; set; }
public string Uri { get; set; }
public string SecretKey { get; set; }
}

View File

@ -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.ResponseDto;
public class WebhooksConfigDto
{
public string Uri { get; set; }
}