committed by
GitHub
189 changed files with 7134 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.HttpApi.Client\LINGYUN.Abp.WebhooksManagement.HttpApi.Client.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Webhooks\LINGYUN.Abp.Webhooks.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,10 @@ |
|||
using LINGYUN.Abp.WebhooksManagement; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.ClientProxies; |
|||
|
|||
[DependsOn(typeof(AbpWebhooksModule))] |
|||
[DependsOn(typeof(WebhooksManagementHttpApiClientModule))] |
|||
public class AbpWebHooksClientProxiesModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
using LINGYUN.Abp.WebhooksManagement; |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.ClientProxies; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
public class ClientProxiesWebhookPublisher : IWebhookPublisher, ITransientDependency |
|||
{ |
|||
protected IWebhookPublishAppService PublishAppService { get; } |
|||
|
|||
public ClientProxiesWebhookPublisher( |
|||
IWebhookPublishAppService publishAppService) |
|||
{ |
|||
PublishAppService = publishAppService; |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null) |
|||
{ |
|||
var input = new WebhookPublishInput |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
SendExactSameData = sendExactSameData, |
|||
}; |
|||
if (headers != null) |
|||
{ |
|||
input.Header = new WebhooksHeaderInput |
|||
{ |
|||
UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, |
|||
Headers = headers.Headers |
|||
}; |
|||
} |
|||
|
|||
await PublishAsync(input); |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null) |
|||
{ |
|||
var input = new WebhookPublishInput |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
SendExactSameData = sendExactSameData, |
|||
TenantIds = new List<Guid?> |
|||
{ |
|||
tenantId |
|||
}, |
|||
}; |
|||
if (headers != null) |
|||
{ |
|||
input.Header = new WebhooksHeaderInput |
|||
{ |
|||
UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, |
|||
Headers = headers.Headers |
|||
}; |
|||
} |
|||
|
|||
await PublishAsync(input); |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null) |
|||
{ |
|||
var input = new WebhookPublishInput |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
SendExactSameData = sendExactSameData, |
|||
TenantIds = tenantIds.ToList(), |
|||
}; |
|||
if (headers != null) |
|||
{ |
|||
input.Header = new WebhooksHeaderInput |
|||
{ |
|||
UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, |
|||
Headers = headers.Headers |
|||
}; |
|||
} |
|||
|
|||
await PublishAsync(input); |
|||
} |
|||
|
|||
protected virtual async Task PublishAsync(WebhookPublishInput input) |
|||
{ |
|||
await PublishAppService.PublishAsync(input); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Features" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Guids" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,54 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks; |
|||
|
|||
//[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
|
|||
// 防止未引用实现无法发布到后台作业
|
|||
[DependsOn(typeof(AbpBackgroundJobsModule))] |
|||
[DependsOn(typeof(AbpFeaturesModule))] |
|||
[DependsOn(typeof(AbpGuidsModule))] |
|||
[DependsOn(typeof(AbpHttpClientModule))] |
|||
public class AbpWebhooksModule : AbpModule |
|||
{ |
|||
internal const string WebhooksClient = "__Abp_Webhooks_HttpClient"; |
|||
|
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
AutoAddDefinitionProviders(context.Services); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var options = context.Services.ExecutePreConfiguredActions<AbpWebhooksOptions>(); |
|||
|
|||
context.Services.AddHttpClient(WebhooksClient, client => |
|||
{ |
|||
client.Timeout = options.TimeoutDuration; |
|||
}); |
|||
} |
|||
|
|||
private static void AutoAddDefinitionProviders(IServiceCollection services) |
|||
{ |
|||
var definitionProviders = new List<Type>(); |
|||
|
|||
services.OnRegistred(context => |
|||
{ |
|||
if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
definitionProviders.Add(context.ImplementationType); |
|||
} |
|||
}); |
|||
|
|||
services.Configure<AbpWebhooksOptions>(options => |
|||
{ |
|||
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks; |
|||
|
|||
public class AbpWebhooksOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 默认超时时间
|
|||
/// </summary>
|
|||
public TimeSpan TimeoutDuration { get; set; } |
|||
/// <summary>
|
|||
/// 默认最大发送次数
|
|||
/// </summary>
|
|||
public int MaxSendAttemptCount { get; set; } |
|||
/// <summary>
|
|||
/// 是否达到最大连续失败次数时自动取消订阅
|
|||
/// </summary>
|
|||
public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; } |
|||
/// <summary>
|
|||
/// 取消订阅前最大连续失败次数
|
|||
/// </summary>
|
|||
public int MaxConsecutiveFailCountBeforeDeactivateSubscription { get; set; } |
|||
|
|||
public ITypeList<WebhookDefinitionProvider> DefinitionProviders { get; } |
|||
|
|||
public AbpWebhooksOptions() |
|||
{ |
|||
TimeoutDuration = TimeSpan.FromSeconds(60); |
|||
MaxSendAttemptCount = 5; |
|||
MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; |
|||
|
|||
DefinitionProviders = new TypeList<WebhookDefinitionProvider>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,131 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.BackgroundWorker |
|||
{ |
|||
public class WebhookSenderJob : AsyncBackgroundJob<WebhookSenderArgs>, ITransientDependency |
|||
{ |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly IWebhookDefinitionManager _webhookDefinitionManager; |
|||
private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; |
|||
private readonly IWebhookSendAttemptStore _webhookSendAttemptStore; |
|||
private readonly IWebhookSender _webhookSender; |
|||
|
|||
private readonly AbpWebhooksOptions _options; |
|||
|
|||
public WebhookSenderJob( |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
IWebhookDefinitionManager webhookDefinitionManager, |
|||
IWebhookSubscriptionManager webhookSubscriptionManager, |
|||
IWebhookSendAttemptStore webhookSendAttemptStore, |
|||
IWebhookSender webhookSender, |
|||
IOptions<AbpWebhooksOptions> options) |
|||
{ |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_webhookDefinitionManager = webhookDefinitionManager; |
|||
_webhookSubscriptionManager = webhookSubscriptionManager; |
|||
_webhookSendAttemptStore = webhookSendAttemptStore; |
|||
_webhookSender = webhookSender; |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public override async Task ExecuteAsync(WebhookSenderArgs args) |
|||
{ |
|||
var webhookDefinition = _webhookDefinitionManager.Get(args.WebhookName); |
|||
|
|||
if (webhookDefinition.TryOnce) |
|||
{ |
|||
try |
|||
{ |
|||
await SendWebhook(args, webhookDefinition); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogWarning("An error occured while sending webhook with try once.", e); |
|||
// ignored
|
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await SendWebhook(args, webhookDefinition); |
|||
} |
|||
} |
|||
|
|||
private async Task SendWebhook(WebhookSenderArgs args, WebhookDefinition webhookDefinition) |
|||
{ |
|||
if (args.WebhookEventId == default) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (args.WebhookSubscriptionId == default) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!webhookDefinition.TryOnce) |
|||
{ |
|||
var sendAttemptCount = await _webhookSendAttemptStore.GetSendAttemptCountAsync( |
|||
args.TenantId, |
|||
args.WebhookEventId, |
|||
args.WebhookSubscriptionId |
|||
); |
|||
|
|||
if ((webhookDefinition.MaxSendAttemptCount > 0 && sendAttemptCount > webhookDefinition.MaxSendAttemptCount) || |
|||
sendAttemptCount > _options.MaxSendAttemptCount) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
try |
|||
{ |
|||
await _webhookSender.SendWebhookAsync(args); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
// no need to retry to send webhook since subscription disabled
|
|||
if (!await TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount( |
|||
args.TenantId, |
|||
args.WebhookSubscriptionId)) |
|||
{ |
|||
throw; //Throw exception to re-try sending webhook
|
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<bool> TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount( |
|||
Guid? tenantId, |
|||
Guid subscriptionId) |
|||
{ |
|||
if (!_options.IsAutomaticSubscriptionDeactivationEnabled) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var hasXConsecutiveFail = await _webhookSendAttemptStore |
|||
.HasXConsecutiveFailAsync( |
|||
tenantId, |
|||
subscriptionId, |
|||
_options.MaxConsecutiveFailCountBeforeDeactivateSubscription |
|||
); |
|||
|
|||
if (!hasXConsecutiveFail) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await _webhookSubscriptionManager.ActivateWebhookSubscriptionAsync(subscriptionId, false); |
|||
await uow.CompleteAsync(); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class DefaultWebhookPublisher : IWebhookPublisher, ITransientDependency |
|||
{ |
|||
public IWebhookEventStore WebhookEventStore { get; set; } |
|||
|
|||
private readonly ICurrentTenant _currentTenant; |
|||
private readonly IBackgroundJobManager _backgroundJobManager; |
|||
private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; |
|||
|
|||
public DefaultWebhookPublisher( |
|||
IWebhookSubscriptionManager webhookSubscriptionManager, |
|||
ICurrentTenant currentTenant, |
|||
IBackgroundJobManager backgroundJobManager) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
_backgroundJobManager = backgroundJobManager; |
|||
_webhookSubscriptionManager = webhookSubscriptionManager; |
|||
|
|||
WebhookEventStore = NullWebhookEventStore.Instance; |
|||
} |
|||
|
|||
#region Async Publish Methods
|
|||
|
|||
public virtual async Task PublishAsync( |
|||
string webhookName, |
|||
object data, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(_currentTenant.Id, webhookName); |
|||
await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); |
|||
} |
|||
|
|||
public virtual async Task PublishAsync( |
|||
string webhookName, |
|||
object data, |
|||
Guid? tenantId, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(tenantId, webhookName); |
|||
await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); |
|||
} |
|||
|
|||
public virtual async Task PublishAsync( |
|||
Guid?[] tenantIds, |
|||
string webhookName, |
|||
object data, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(tenantIds, webhookName); |
|||
await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); |
|||
} |
|||
|
|||
protected virtual async Task PublishAsync( |
|||
string webhookName, |
|||
object data, |
|||
List<WebhookSubscriptionInfo> webhookSubscriptions, |
|||
bool sendExactSameData = false, |
|||
WebhookHeader headers = null) |
|||
{ |
|||
if (webhookSubscriptions.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var subscriptionsGroupedByTenant = webhookSubscriptions.GroupBy(x => x.TenantId); |
|||
|
|||
foreach (var subscriptionGroupedByTenant in subscriptionsGroupedByTenant) |
|||
{ |
|||
var webhookInfo = await SaveAndGetWebhookAsync(subscriptionGroupedByTenant.Key, webhookName, data); |
|||
|
|||
foreach (var webhookSubscription in subscriptionGroupedByTenant) |
|||
{ |
|||
var headersToSend = webhookSubscription.Headers; |
|||
if (headers != null) |
|||
{ |
|||
if (headers.UseOnlyGivenHeaders)//do not use the headers defined in subscription
|
|||
{ |
|||
headersToSend = headers.Headers; |
|||
} |
|||
else |
|||
{ |
|||
//use the headers defined in subscription. If additional headers has same header, use additional headers value.
|
|||
foreach (var additionalHeader in headers.Headers) |
|||
{ |
|||
headersToSend[additionalHeader.Key] = additionalHeader.Value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await _backgroundJobManager.EnqueueAsync(new WebhookSenderArgs |
|||
{ |
|||
TenantId = webhookSubscription.TenantId, |
|||
WebhookEventId = webhookInfo.Id, |
|||
Data = webhookInfo.Data, |
|||
WebhookName = webhookInfo.WebhookName, |
|||
WebhookSubscriptionId = webhookSubscription.Id, |
|||
Headers = headersToSend, |
|||
Secret = webhookSubscription.Secret, |
|||
WebhookUri = webhookSubscription.WebhookUri, |
|||
SendExactSameData = sendExactSameData |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
protected virtual async Task<WebhookEvent> SaveAndGetWebhookAsync( |
|||
Guid? tenantId, |
|||
string webhookName, |
|||
object data) |
|||
{ |
|||
var webhookInfo = new WebhookEvent |
|||
{ |
|||
WebhookName = webhookName, |
|||
Data = JsonConvert.SerializeObject(data), |
|||
TenantId = tenantId |
|||
}; |
|||
|
|||
var webhookId = await WebhookEventStore.InsertAndGetIdAsync(webhookInfo); |
|||
webhookInfo.Id = webhookId; |
|||
|
|||
return webhookInfo; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using System; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class DefaultWebhookSender : IWebhookSender, ITransientDependency |
|||
{ |
|||
public ILogger<DefaultWebhookSender> Logger { protected get; set; } |
|||
|
|||
private readonly IWebhookManager _webhookManager; |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
|
|||
private const string FailedRequestDefaultContent = "Webhook Send Request Failed"; |
|||
|
|||
public DefaultWebhookSender( |
|||
IWebhookManager webhookManager, |
|||
IHttpClientFactory httpClientFactory) |
|||
{ |
|||
_webhookManager = webhookManager; |
|||
_httpClientFactory = httpClientFactory; |
|||
|
|||
Logger = NullLogger<DefaultWebhookSender>.Instance; |
|||
} |
|||
|
|||
public async Task<Guid> SendWebhookAsync(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
if (webhookSenderArgs.WebhookEventId == default) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookEventId)); |
|||
} |
|||
|
|||
if (webhookSenderArgs.WebhookSubscriptionId == default) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookSenderArgs.WebhookSubscriptionId)); |
|||
} |
|||
|
|||
var webhookSendAttemptId = await _webhookManager.InsertAndGetIdWebhookSendAttemptAsync(webhookSenderArgs); |
|||
|
|||
var request = CreateWebhookRequestMessage(webhookSenderArgs); |
|||
|
|||
var serializedBody = await _webhookManager.GetSerializedBodyAsync(webhookSenderArgs); |
|||
|
|||
_webhookManager.SignWebhookRequest(request, serializedBody, webhookSenderArgs.Secret); |
|||
|
|||
AddAdditionalHeaders(request, webhookSenderArgs); |
|||
|
|||
var isSucceed = false; |
|||
HttpStatusCode? statusCode = null; |
|||
var content = FailedRequestDefaultContent; |
|||
|
|||
try |
|||
{ |
|||
var response = await SendHttpRequest(request); |
|||
isSucceed = response.isSucceed; |
|||
statusCode = response.statusCode; |
|||
content = response.content; |
|||
} |
|||
catch (TaskCanceledException) |
|||
{ |
|||
statusCode = HttpStatusCode.RequestTimeout; |
|||
content = "Request Timeout"; |
|||
} |
|||
catch (HttpRequestException e) |
|||
{ |
|||
content = e.Message; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogError("An error occured while sending a webhook request", e); |
|||
} |
|||
finally |
|||
{ |
|||
await _webhookManager.StoreResponseOnWebhookSendAttemptAsync(webhookSendAttemptId, webhookSenderArgs.TenantId, statusCode, content); |
|||
} |
|||
|
|||
if (!isSucceed) |
|||
{ |
|||
throw new Exception($"Webhook sending attempt failed. WebhookSendAttempt id: {webhookSendAttemptId}"); |
|||
} |
|||
|
|||
return webhookSendAttemptId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// You can override this to change request message
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
return new HttpRequestMessage(HttpMethod.Post, webhookSenderArgs.WebhookUri); |
|||
} |
|||
|
|||
protected virtual void AddAdditionalHeaders(HttpRequestMessage request, WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
foreach (var header in webhookSenderArgs.Headers) |
|||
{ |
|||
if (request.Headers.TryAddWithoutValidation(header.Key, header.Value)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
throw new Exception($"Invalid Header. SubscriptionId:{webhookSenderArgs.WebhookSubscriptionId},Header: {header.Key}:{header.Value}"); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<(bool isSucceed, HttpStatusCode statusCode, string content)> SendHttpRequest(HttpRequestMessage request) |
|||
{ |
|||
var client = _httpClientFactory.CreateClient(AbpWebhooksModule.WebhooksClient); |
|||
|
|||
var response = await client.SendAsync(request); |
|||
|
|||
var isSucceed = response.IsSuccessStatusCode; |
|||
var statusCode = response.StatusCode; |
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
|
|||
return (isSucceed, statusCode, content); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Extensions |
|||
{ |
|||
public static class WebhookSubscriptionExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// checks if subscribed to given webhook
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName) |
|||
{ |
|||
if (webhookSubscription.Webhooks.IsNullOrEmpty()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return webhookSubscription.Webhooks.Contains(webhookName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookDefinitionContext |
|||
{ |
|||
WebhookGroupDefinition AddGroup( |
|||
[NotNull] string name, |
|||
ILocalizableString displayName = null); |
|||
|
|||
WebhookGroupDefinition GetGroupOrNull(string name); |
|||
|
|||
void RemoveGroup(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookDefinitionManager |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a webhook definition by name.
|
|||
/// Returns null if there is no webhook definition with given name.
|
|||
/// </summary>
|
|||
WebhookDefinition GetOrNull(string name); |
|||
|
|||
/// <summary>
|
|||
/// Gets a webhook definition by name.
|
|||
/// Throws exception if there is no webhook definition with given name.
|
|||
/// </summary>
|
|||
WebhookDefinition Get(string name); |
|||
|
|||
/// <summary>
|
|||
/// Gets all webhook definitions.
|
|||
/// </summary>
|
|||
IReadOnlyList<WebhookDefinition> GetAll(); |
|||
|
|||
/// <summary>
|
|||
/// Gets all webhook group definitions.
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
IReadOnlyList<WebhookGroupDefinition> GetGroups(); |
|||
|
|||
/// <summary>
|
|||
/// Checks if given webhook name is available for given tenant.
|
|||
/// </summary>
|
|||
Task<bool> IsAvailableAsync(Guid? tenantId, string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookEventStore |
|||
{ |
|||
/// <summary>
|
|||
/// Inserts to persistent store
|
|||
/// </summary>
|
|||
Task<Guid> InsertAndGetIdAsync(WebhookEvent webhookEvent); |
|||
|
|||
/// <summary>
|
|||
/// Gets Webhook info by id
|
|||
/// </summary>
|
|||
Task<WebhookEvent> GetAsync(Guid? tenantId, Guid id); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookManager |
|||
{ |
|||
Task<WebhookPayload> GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret); |
|||
|
|||
Task<string> GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
Task StoreResponseOnWebhookSendAttemptAsync( |
|||
Guid webhookSendAttemptId, Guid? tenantId, |
|||
HttpStatusCode? statusCode, string content); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookPublisher |
|||
{ |
|||
/// <summary>
|
|||
/// Sends webhooks to current tenant subscriptions (<see cref="IAbpSession.TenantId"/>). with given data, (Checks permissions)
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="data">data to send</param>
|
|||
/// <param name="sendExactSameData">
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </param>
|
|||
/// <param name="headers">Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.</param>
|
|||
Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); |
|||
|
|||
/// <summary>
|
|||
/// Sends webhooks to given tenant's subscriptions
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="data">data to send</param>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id
|
|||
/// </param>
|
|||
/// <param name="sendExactSameData">
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </param>
|
|||
/// <param name="headers">Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.</param>
|
|||
Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null); |
|||
|
|||
/// <summary>
|
|||
/// Sends webhooks to given tenant's subscriptions
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="data">data to send</param>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s)
|
|||
/// </param>
|
|||
/// <param name="sendExactSameData">
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </param>
|
|||
/// <param name="headers">Headers to send. Publisher uses subscription defined webhook by default. You can add additional headers from here. If subscription already has given header, publisher uses the one you give here.</param>
|
|||
Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookSendAttemptStore |
|||
{ |
|||
Task<WebhookSendAttempt> GetAsync(Guid? tenantId, Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook)
|
|||
/// </summary>
|
|||
Task<int> GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId); |
|||
|
|||
/// <summary>
|
|||
/// Checks is there any successful webhook attempt in last <paramref name="searchCount"/> items. Should return true if there are not X number items
|
|||
/// </summary>
|
|||
Task<bool> HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount); |
|||
|
|||
Task<(int TotalCount, IReadOnlyCollection<WebhookSendAttempt> Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount); |
|||
|
|||
Task<List<WebhookSendAttempt>> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookSender |
|||
{ |
|||
/// <summary>
|
|||
/// Tries to send webhook with given transactionId and stores process in <see cref="WebhookSendAttempt"/>
|
|||
/// Should throw exception if fails or response status not succeed
|
|||
/// </summary>
|
|||
/// <param name="webhookSenderArgs">arguments</param>
|
|||
/// <returns>Webhook send attempt id</returns>
|
|||
Task<Guid> SendWebhookAsync(WebhookSenderArgs webhookSenderArgs); |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public interface IWebhookSubscriptionManager |
|||
{ |
|||
/// <summary>
|
|||
/// Returns subscription for given id.
|
|||
/// </summary>
|
|||
/// <param name="id">Unique identifier of <see cref="WebhookSubscriptionInfo"/></param>
|
|||
Task<WebhookSubscriptionInfo> GetAsync(Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of tenant
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions for given webhook.
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of tenant
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions for given webhook.
|
|||
/// </summary>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted)
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted)
|
|||
/// </summary>
|
|||
Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription); |
|||
|
|||
/// <summary>
|
|||
/// Activates/Deactivates given webhook subscription
|
|||
/// </summary>
|
|||
/// <param name="id">unique identifier of <see cref="WebhookSubscriptionInfo"/></param>
|
|||
/// <param name="active">IsActive</param>
|
|||
Task ActivateWebhookSubscriptionAsync(Guid id, bool active); |
|||
|
|||
/// <summary>
|
|||
/// Delete given webhook subscription.
|
|||
/// </summary>
|
|||
/// <param name="id">unique identifier of <see cref="WebhookSubscriptionInfo"/></param>
|
|||
Task DeleteSubscriptionAsync(Guid id); |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// This interface should be implemented by vendors to make webhooks working.
|
|||
/// </summary>
|
|||
public interface IWebhookSubscriptionsStore |
|||
{ |
|||
/// <summary>
|
|||
/// returns subscription
|
|||
/// </summary>
|
|||
/// <param name="id">webhook subscription id</param>
|
|||
/// <returns></returns>
|
|||
Task<WebhookSubscriptionInfo> GetAsync(Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Saves webhook subscription to a persistent store.
|
|||
/// </summary>
|
|||
/// <param name="webhookSubscription">webhook subscription information</param>
|
|||
Task InsertAsync(WebhookSubscriptionInfo webhookSubscription); |
|||
|
|||
/// <summary>
|
|||
/// Updates webhook subscription to a persistent store.
|
|||
/// </summary>
|
|||
/// <param name="webhookSubscription">webhook subscription information</param>
|
|||
Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription); |
|||
|
|||
/// <summary>
|
|||
/// Deletes subscription if exists
|
|||
/// </summary>
|
|||
/// <param name="id"><see cref="WebhookSubscriptionInfo"/> primary key</param>
|
|||
/// <returns></returns>
|
|||
Task DeleteAsync(Guid id); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of given tenant including deactivated
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId); |
|||
|
|||
/// <summary>
|
|||
/// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id.
|
|||
/// </param>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <returns></returns>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Returns all subscriptions of given tenant including deactivated
|
|||
/// </summary>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); |
|||
|
|||
/// <summary>
|
|||
/// Returns webhook subscriptions which subscribe to given webhook on tenant(s)
|
|||
/// </summary>
|
|||
/// <param name="tenantIds">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
/// <param name="webhookName"><see cref="WebhookDefinition.Name"/></param>
|
|||
/// <returns></returns>
|
|||
Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName); |
|||
|
|||
/// <summary>
|
|||
/// Checks if tenant subscribed for a webhook
|
|||
/// </summary>
|
|||
/// <param name="tenantId">
|
|||
/// Target tenant id(s).
|
|||
/// </param>
|
|||
/// <param name="webhookName">Name of the webhook</param>
|
|||
Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Null pattern implementation of <see cref="IWebhookSubscriptionsStore"/>.
|
|||
/// It's used if <see cref="IWebhookSubscriptionsStore"/> is not implemented by actual persistent store
|
|||
/// </summary>
|
|||
public class NullWebhookEventStore : IWebhookEventStore |
|||
{ |
|||
public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore(); |
|||
|
|||
public Task<Guid> InsertAndGetIdAsync(WebhookEvent webhookEvent) |
|||
{ |
|||
return Task.FromResult<Guid>(default); |
|||
} |
|||
|
|||
public Task<WebhookEvent> GetAsync(Guid? tenantId, Guid id) |
|||
{ |
|||
return Task.FromResult<WebhookEvent>(default); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class NullWebhookSendAttemptStore : IWebhookSendAttemptStore |
|||
{ |
|||
public static NullWebhookSendAttemptStore Instance = new NullWebhookSendAttemptStore(); |
|||
|
|||
public Task InsertAsync(WebhookSendAttempt webhookSendAttempt) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task UpdateAsync(WebhookSendAttempt webhookSendAttempt) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task<WebhookSendAttempt> GetAsync(Guid? tenantId, Guid id) |
|||
{ |
|||
return Task.FromResult<WebhookSendAttempt>(default); |
|||
} |
|||
|
|||
public Task<int> GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId) |
|||
{ |
|||
return Task.FromResult(int.MaxValue); |
|||
} |
|||
|
|||
public Task<bool> HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
public Task<(int TotalCount, IReadOnlyCollection<WebhookSendAttempt> Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, |
|||
int skipCount) |
|||
{ |
|||
return Task.FromResult(ValueTuple.Create(0, new List<WebhookSendAttempt>() as IReadOnlyCollection<WebhookSendAttempt>)); |
|||
} |
|||
|
|||
public Task<List<WebhookSendAttempt>> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSendAttempt>()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Null pattern implementation of <see cref="IWebhookSubscriptionsStore"/>.
|
|||
/// It's used if <see cref="IWebhookSubscriptionsStore"/> is not implemented by actual persistent store
|
|||
/// </summary>
|
|||
public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore |
|||
{ |
|||
public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore(); |
|||
|
|||
public Task<WebhookSubscriptionInfo> GetAsync(Guid id) |
|||
{ |
|||
return Task.FromResult<WebhookSubscriptionInfo>(default); |
|||
} |
|||
|
|||
public WebhookSubscriptionInfo Get(Guid id) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
public Task InsertAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task DeleteAsync(Guid id) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) |
|||
{ |
|||
return Task.FromResult(new List<WebhookSubscriptionInfo>()); |
|||
} |
|||
|
|||
public Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookDefinition |
|||
{ |
|||
/// <summary>
|
|||
/// Unique name of the webhook.
|
|||
/// </summary>
|
|||
public string Name { get; } |
|||
|
|||
/// <summary>
|
|||
/// Tries to send webhook only one time without checking to send attempt count
|
|||
/// </summary>
|
|||
public bool TryOnce { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Defined maximum number of sending times
|
|||
/// </summary>
|
|||
public int MaxSendAttemptCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Display name of the webhook.
|
|||
/// Optional.
|
|||
/// </summary>
|
|||
public ILocalizableString DisplayName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Description for the webhook.
|
|||
/// Optional.
|
|||
/// </summary>
|
|||
public ILocalizableString Description { get; set; } |
|||
|
|||
public List<string> RequiredFeatures { get; set; } |
|||
|
|||
public WebhookDefinition(string name, ILocalizableString displayName = null, ILocalizableString description = null) |
|||
{ |
|||
if (name.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(name), $"{nameof(name)} can not be null, empty or whitespace!"); |
|||
} |
|||
|
|||
Name = name.Trim(); |
|||
DisplayName = displayName; |
|||
Description = description; |
|||
|
|||
RequiredFeatures = new List<string>(); |
|||
} |
|||
|
|||
public WebhookDefinition WithFeature(params string[] features) |
|||
{ |
|||
if (!features.IsNullOrEmpty()) |
|||
{ |
|||
RequiredFeatures.AddRange(features); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(WebhookDefinition)} {Name}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookDefinitionContext : IWebhookDefinitionContext |
|||
{ |
|||
protected Dictionary<string, WebhookGroupDefinition> Groups { get; } |
|||
|
|||
public WebhookDefinitionContext(Dictionary<string, WebhookGroupDefinition> webhooks) |
|||
{ |
|||
Groups = webhooks; |
|||
} |
|||
|
|||
public WebhookGroupDefinition AddGroup( |
|||
[NotNull] string name, |
|||
ILocalizableString displayName = null) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (Groups.ContainsKey(name)) |
|||
{ |
|||
throw new AbpException($"There is already an existing webhook group with name: {name}"); |
|||
} |
|||
|
|||
return Groups[name] = new WebhookGroupDefinition(name, displayName); |
|||
} |
|||
|
|||
public WebhookGroupDefinition GetGroupOrNull([NotNull] string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (!Groups.ContainsKey(name)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return Groups[name]; |
|||
} |
|||
|
|||
public void RemoveGroup(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (!Groups.ContainsKey(name)) |
|||
{ |
|||
throw new AbpException($"Undefined notification webhook group: '{name}'."); |
|||
} |
|||
|
|||
Groups.Remove(name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency |
|||
{ |
|||
protected IDictionary<string, WebhookGroupDefinition> WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value; |
|||
private readonly Lazy<Dictionary<string, WebhookGroupDefinition>> _lazyWebhookGroupDefinitions; |
|||
|
|||
protected IDictionary<string, WebhookDefinition> WebhookDefinitions => _lazyWebhookDefinitions.Value; |
|||
private readonly Lazy<Dictionary<string, WebhookDefinition>> _lazyWebhookDefinitions; |
|||
|
|||
private readonly IServiceProvider _serviceProvider; |
|||
private readonly AbpWebhooksOptions _options; |
|||
|
|||
public WebhookDefinitionManager( |
|||
IServiceProvider serviceProvider, |
|||
IOptions<AbpWebhooksOptions> options) |
|||
{ |
|||
_serviceProvider = serviceProvider; |
|||
_options = options.Value; |
|||
|
|||
_lazyWebhookGroupDefinitions = new Lazy<Dictionary<string, WebhookGroupDefinition>>(CreateWebhookGroupDefinitions); |
|||
_lazyWebhookDefinitions = new Lazy<Dictionary<string, WebhookDefinition>>(CreateWebhookDefinitions); |
|||
} |
|||
|
|||
public WebhookDefinition GetOrNull(string name) |
|||
{ |
|||
if (!WebhookDefinitions.ContainsKey(name)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return WebhookDefinitions[name]; |
|||
} |
|||
|
|||
public WebhookDefinition Get(string name) |
|||
{ |
|||
if (!WebhookDefinitions.ContainsKey(name)) |
|||
{ |
|||
throw new KeyNotFoundException($"Webhook definitions does not contain a definition with the key \"{name}\"."); |
|||
} |
|||
|
|||
return WebhookDefinitions[name]; |
|||
} |
|||
|
|||
public IReadOnlyList<WebhookDefinition> GetAll() |
|||
{ |
|||
return WebhookDefinitions.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public IReadOnlyList<WebhookGroupDefinition> GetGroups() |
|||
{ |
|||
return WebhookGroupDefinitions.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public async Task<bool> IsAvailableAsync(Guid? tenantId, string name) |
|||
{ |
|||
if (tenantId == null) // host allowed to subscribe all webhooks
|
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
var webhookDefinition = GetOrNull(name); |
|||
|
|||
if (webhookDefinition == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (webhookDefinition.RequiredFeatures?.Any() == false) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
var currentTenant = _serviceProvider.GetRequiredService<ICurrentTenant>(); |
|||
var featureChecker = _serviceProvider.GetRequiredService<IFeatureChecker>(); |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray())) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
protected virtual Dictionary<string, WebhookDefinition> CreateWebhookDefinitions() |
|||
{ |
|||
var definitions = new Dictionary<string, WebhookDefinition>(); |
|||
|
|||
foreach (var groupDefinition in WebhookGroupDefinitions.Values) |
|||
{ |
|||
foreach (var webhook in groupDefinition.Webhooks) |
|||
{ |
|||
if (definitions.ContainsKey(webhook.Name)) |
|||
{ |
|||
throw new AbpException("Duplicate webhook name: " + webhook.Name); |
|||
} |
|||
|
|||
definitions[webhook.Name] = webhook; |
|||
} |
|||
} |
|||
|
|||
return definitions; |
|||
} |
|||
|
|||
protected virtual Dictionary<string, WebhookGroupDefinition> CreateWebhookGroupDefinitions() |
|||
{ |
|||
var definitions = new Dictionary<string, WebhookGroupDefinition>(); |
|||
|
|||
using (var scope = _serviceProvider.CreateScope()) |
|||
{ |
|||
var providers = _options |
|||
.DefinitionProviders |
|||
.Select(p => scope.ServiceProvider.GetRequiredService(p) as WebhookDefinitionProvider) |
|||
.ToList(); |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.Define(new WebhookDefinitionContext(definitions)); |
|||
} |
|||
} |
|||
|
|||
return definitions; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public abstract class WebhookDefinitionProvider : ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
/// Used to add/manipulate webhook definitions.
|
|||
/// </summary>
|
|||
/// <param name="context">Context</param>,
|
|||
public abstract void Define(IWebhookDefinitionContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Store created web hooks. To see who get that webhook check with <see cref="WebhookSendAttempt.WebhookEventId"/> and you can get <see cref="WebhookSendAttempt.WebhookSubscriptionId"/>
|
|||
/// </summary>
|
|||
public class WebhookEvent |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook unique name <see cref="WebhookDefinition.Name"/>
|
|||
/// </summary>
|
|||
public string WebhookName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook data as JSON string.
|
|||
/// </summary>
|
|||
public string Data { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public bool IsDeleted { get; set; } |
|||
|
|||
public DateTime? DeletionTime { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks; |
|||
|
|||
public class WebhookGroupDefinition |
|||
{ |
|||
[NotNull] |
|||
public string Name { get; set; } |
|||
public Dictionary<string, object> Properties { get; } |
|||
|
|||
private ILocalizableString _displayName; |
|||
public ILocalizableString DisplayName |
|||
{ |
|||
get { |
|||
return _displayName; |
|||
} |
|||
set { |
|||
_displayName = value; |
|||
} |
|||
} |
|||
|
|||
public IReadOnlyList<WebhookDefinition> Webhooks => _webhooks.ToImmutableList(); |
|||
private readonly List<WebhookDefinition> _webhooks; |
|||
public object this[string name] { |
|||
get => Properties.GetOrDefault(name); |
|||
set => Properties[name] = value; |
|||
} |
|||
|
|||
protected internal WebhookGroupDefinition( |
|||
string name, |
|||
ILocalizableString displayName = null) |
|||
{ |
|||
Name = name; |
|||
DisplayName = displayName ?? new FixedLocalizableString(Name); |
|||
|
|||
Properties = new Dictionary<string, object>(); |
|||
_webhooks = new List<WebhookDefinition>(); |
|||
} |
|||
|
|||
public virtual WebhookDefinition AddWebhook( |
|||
string name, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null) |
|||
{ |
|||
if (Webhooks.Any(hook => hook.Name.Equals(name))) |
|||
{ |
|||
throw new AbpException($"There is already an existing webhook with name: {name} in group {Name}"); |
|||
} |
|||
|
|||
var webhook = new WebhookDefinition( |
|||
name, |
|||
displayName, |
|||
description |
|||
); |
|||
|
|||
_webhooks.Add(webhook); |
|||
|
|||
return webhook; |
|||
} |
|||
|
|||
public virtual void AddWebhooks(params WebhookDefinition[] webhooks) |
|||
{ |
|||
foreach (var webhook in webhooks) |
|||
{ |
|||
if (Webhooks.Any(hook => hook.Name.Equals(webhook.Name))) |
|||
{ |
|||
throw new AbpException($"There is already an existing webhook with name: {webhook.Name} in group {Name}"); |
|||
} |
|||
} |
|||
|
|||
_webhooks.AddRange(webhooks); |
|||
} |
|||
|
|||
|
|||
|
|||
[CanBeNull] |
|||
public WebhookDefinition GetWebhookOrNull([NotNull] string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
foreach (var webhook in Webhooks) |
|||
{ |
|||
if (webhook.Name == name) |
|||
{ |
|||
return webhook; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(WebhookGroupDefinition)} {Name}]"; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookHeader |
|||
{ |
|||
/// <summary>
|
|||
/// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription.
|
|||
/// Default is false
|
|||
/// </summary>
|
|||
public bool UseOnlyGivenHeaders { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// That headers will be sent with the webhook.
|
|||
/// </summary>
|
|||
public IDictionary<string, string> Headers { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public abstract class WebhookManager : IWebhookManager |
|||
{ |
|||
private const string SignatureHeaderKey = "sha256"; |
|||
private const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}"; |
|||
private const string SignatureHeaderName = "abp-webhook-signature"; |
|||
protected IWebhookSendAttemptStore WebhookSendAttemptStore { get; } |
|||
|
|||
protected WebhookManager( |
|||
IWebhookSendAttemptStore webhookSendAttemptStore) |
|||
{ |
|||
WebhookSendAttemptStore = webhookSendAttemptStore; |
|||
} |
|||
|
|||
public virtual async Task<WebhookPayload> GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
var data = JsonConvert.SerializeObject(webhookSenderArgs.Data); |
|||
|
|||
var attemptNumber = await WebhookSendAttemptStore.GetSendAttemptCountAsync( |
|||
webhookSenderArgs.TenantId, |
|||
webhookSenderArgs.WebhookEventId, |
|||
webhookSenderArgs.WebhookSubscriptionId); |
|||
|
|||
return new WebhookPayload( |
|||
webhookSenderArgs.WebhookEventId.ToString(), |
|||
webhookSenderArgs.WebhookName, |
|||
attemptNumber) |
|||
{ |
|||
Data = data |
|||
}; |
|||
} |
|||
|
|||
public virtual void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret) |
|||
{ |
|||
if (request == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(request)); |
|||
} |
|||
|
|||
if (string.IsNullOrWhiteSpace(serializedBody)) |
|||
{ |
|||
throw new ArgumentNullException(nameof(serializedBody)); |
|||
} |
|||
|
|||
request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json"); |
|||
|
|||
var secretBytes = Encoding.UTF8.GetBytes(secret); |
|||
var headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, serializedBody.Sha256(secretBytes)); |
|||
|
|||
request.Headers.Add(SignatureHeaderName, headerValue); |
|||
} |
|||
|
|||
public virtual async Task<string> GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs) |
|||
{ |
|||
if (webhookSenderArgs.SendExactSameData) |
|||
{ |
|||
return webhookSenderArgs.Data; |
|||
} |
|||
|
|||
var payload = await GetWebhookPayloadAsync(webhookSenderArgs); |
|||
|
|||
var serializedBody = JsonConvert.SerializeObject(payload); |
|||
|
|||
return serializedBody; |
|||
} |
|||
|
|||
public abstract Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); |
|||
|
|||
public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookPayload |
|||
{ |
|||
public string Id { get; set; } |
|||
|
|||
public string WebhookEvent { get; set; } |
|||
|
|||
public int Attempt { get; set; } |
|||
|
|||
public dynamic Data { get; set; } |
|||
|
|||
public DateTime CreationTimeUtc { get; set; } |
|||
|
|||
public WebhookPayload(string id, string webhookEvent, int attempt) |
|||
{ |
|||
if (id.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(id)); |
|||
} |
|||
|
|||
if (webhookEvent.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookEvent)); |
|||
} |
|||
|
|||
Id = id; |
|||
WebhookEvent = webhookEvent; |
|||
Attempt = attempt; |
|||
CreationTimeUtc = DateTime.UtcNow; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using System.Net; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
/// <summary>
|
|||
/// Table for store webhook work items. Each item stores web hook send attempt of <see cref="WebhookEvent"/> to subscribed tenants
|
|||
/// </summary>
|
|||
public class WebhookSendAttempt |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookEvent"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookEventId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookSubscription"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookSubscriptionId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook response content that webhook endpoint send back
|
|||
/// </summary>
|
|||
public string Response { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook response status code that webhook endpoint send back
|
|||
/// </summary>
|
|||
public HttpStatusCode? ResponseStatusCode { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public DateTime? LastModificationTime { get; set; } |
|||
|
|||
public Guid? TenantId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookSenderArgs |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
//Webhook information
|
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookEvent"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookEventId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook unique name
|
|||
/// </summary>
|
|||
public string WebhookName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook data as JSON string.
|
|||
/// </summary>
|
|||
public string Data { get; set; } |
|||
|
|||
//Subscription information
|
|||
|
|||
/// <summary>
|
|||
/// <see cref="WebhookSubscription"/> foreign id
|
|||
/// </summary>
|
|||
public Guid WebhookSubscriptionId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Subscription webhook endpoint
|
|||
/// </summary>
|
|||
public string WebhookUri { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook secret
|
|||
/// </summary>
|
|||
public string Secret { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a set of additional HTTP headers.That headers will be sent with the webhook.
|
|||
/// </summary>
|
|||
public IDictionary<string, string> Headers { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// True: It sends the exact same data as the parameter to clients.
|
|||
/// <para>
|
|||
/// False: It sends data in <see cref="WebhookPayload"/>. It is recommended way.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public bool SendExactSameData { get; set; } |
|||
|
|||
public WebhookSenderArgs() |
|||
{ |
|||
Headers = new Dictionary<string, string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookSubscriptionInfo |
|||
{ |
|||
public Guid Id { get; set; } |
|||
/// <summary>
|
|||
/// Subscribed Tenant's id .
|
|||
/// </summary>
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Subscription webhook endpoint
|
|||
/// </summary>
|
|||
public string WebhookUri { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Webhook secret
|
|||
/// </summary>
|
|||
public string Secret { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Is subscription active
|
|||
/// </summary>
|
|||
public bool IsActive { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Subscribed webhook definitions unique names.It contains webhook definitions list as json
|
|||
/// <para>
|
|||
/// Do not change it manually.
|
|||
/// Use <see cref=" WebhookSubscriptionInfoExtensions.GetSubscribedWebhooks"/>,
|
|||
/// <see cref=" WebhookSubscriptionInfoExtensions.SubscribeWebhook"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.UnsubscribeWebhook"/> and
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.RemoveAllSubscribedWebhooks"/> to change it.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public List<string> Webhooks { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json
|
|||
/// <para>
|
|||
/// Do not change it manually.
|
|||
/// Use <see cref=" WebhookSubscriptionInfoExtensions.GetWebhookHeaders"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.AddWebhookHeader"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.RemoveWebhookHeader"/>,
|
|||
/// <see cref="WebhookSubscriptionInfoExtensions.RemoveAllWebhookHeaders"/> to change it.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public IDictionary<string, string> Headers { get; set; } |
|||
|
|||
public WebhookSubscriptionInfo() |
|||
{ |
|||
IsActive = true; |
|||
Headers = new Dictionary<string, string>(); |
|||
Webhooks = new List<string>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Authorization; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks |
|||
{ |
|||
public class WebhookSubscriptionManager : IWebhookSubscriptionManager, ITransientDependency |
|||
{ |
|||
public IWebhookSubscriptionsStore WebhookSubscriptionsStore { get; set; } |
|||
|
|||
private readonly IGuidGenerator _guidGenerator; |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly IWebhookDefinitionManager _webhookDefinitionManager; |
|||
|
|||
private const string WebhookSubscriptionSecretPrefix = "whs_"; |
|||
|
|||
public WebhookSubscriptionManager( |
|||
IGuidGenerator guidGenerator, |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
IWebhookDefinitionManager webhookDefinitionManager) |
|||
{ |
|||
_guidGenerator = guidGenerator; |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_webhookDefinitionManager = webhookDefinitionManager; |
|||
|
|||
WebhookSubscriptionsStore = NullWebhookSubscriptionsStore.Instance; |
|||
} |
|||
|
|||
public virtual async Task<WebhookSubscriptionInfo> GetAsync(Guid id) |
|||
{ |
|||
return await WebhookSubscriptionsStore.GetAsync(id); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId) |
|||
{ |
|||
return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
return new List<WebhookSubscriptionInfo>(); |
|||
} |
|||
|
|||
return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList(); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) |
|||
{ |
|||
return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList(); |
|||
} |
|||
|
|||
public virtual async Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName) |
|||
{ |
|||
var featureGrantedTenants = new List<Guid?>(); |
|||
foreach (var tenantId in tenantIds) |
|||
{ |
|||
if (await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
featureGrantedTenants.Add(tenantId); |
|||
} |
|||
} |
|||
|
|||
return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(featureGrantedTenants.ToArray(), webhookName)).ToList(); |
|||
} |
|||
|
|||
public virtual async Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return await WebhookSubscriptionsStore.IsSubscribedAsync(tenantId, webhookName); |
|||
} |
|||
|
|||
public virtual async Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await CheckIfPermissionsGrantedAsync(webhookSubscription); |
|||
|
|||
if (webhookSubscription.Id == default) |
|||
{ |
|||
webhookSubscription.Id = _guidGenerator.Create(); |
|||
webhookSubscription.Secret = WebhookSubscriptionSecretPrefix + Guid.NewGuid().ToString("N"); |
|||
await WebhookSubscriptionsStore.InsertAsync(webhookSubscription); |
|||
} |
|||
else |
|||
{ |
|||
await WebhookSubscriptionsStore.UpdateAsync(webhookSubscription); |
|||
} |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task ActivateWebhookSubscriptionAsync(Guid id, bool active) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
var webhookSubscription = await WebhookSubscriptionsStore.GetAsync(id); |
|||
webhookSubscription.IsActive = active; |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task DeleteSubscriptionAsync(Guid id) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await WebhookSubscriptionsStore.DeleteAsync(id); |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task AddWebhookAsync(WebhookSubscriptionInfo subscription, string webhookName) |
|||
{ |
|||
using (var uow = _unitOfWorkManager.Begin()) |
|||
{ |
|||
await CheckPermissionsAsync(subscription.TenantId, webhookName); |
|||
webhookName = webhookName.Trim(); |
|||
if (webhookName.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new ArgumentNullException(nameof(webhookName), $"{nameof(webhookName)} can not be null, empty or whitespace!"); |
|||
} |
|||
|
|||
if (!subscription.Webhooks.Contains(webhookName)) |
|||
{ |
|||
subscription.Webhooks.Add(webhookName); |
|||
|
|||
await WebhookSubscriptionsStore.UpdateAsync(subscription); |
|||
} |
|||
|
|||
await uow.SaveChangesAsync(); |
|||
} |
|||
} |
|||
|
|||
#region PermissionCheck
|
|||
|
|||
protected virtual async Task CheckIfPermissionsGrantedAsync(WebhookSubscriptionInfo webhookSubscription) |
|||
{ |
|||
if (webhookSubscription.Webhooks.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var webhookDefinition in webhookSubscription.Webhooks) |
|||
{ |
|||
await CheckPermissionsAsync(webhookSubscription.TenantId, webhookDefinition); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task CheckPermissionsAsync(Guid? tenantId, string webhookName) |
|||
{ |
|||
if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) |
|||
{ |
|||
throw new AbpAuthorizationException($"Tenant \"{tenantId}\" must have necessary feature(s) to use webhook \"{webhookName}\""); |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Security.Cryptography; |
|||
|
|||
namespace System; |
|||
|
|||
internal static class AbpStringCryptographyExtensions |
|||
{ |
|||
public static string Sha256(this string planText, byte[] salt) |
|||
{ |
|||
var data = planText.GetBytes(); |
|||
using var hmacsha256 = new HMACSHA256(salt); |
|||
return BitConverter.ToString(hmacsha256.ComputeHash(data)); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,30 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\Webhooks\Identity\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="LINGYUN\Abp\Webhooks\Identity\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.EventBus" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Users.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Identity.Domain.Shared" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Webhooks\LINGYUN.Abp.Webhooks.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,32 @@ |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Identity.Localization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Users; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
[DependsOn(typeof(AbpDddDomainModule))] |
|||
[DependsOn(typeof(AbpEventBusModule))] |
|||
[DependsOn(typeof(AbpUsersAbstractionModule))] |
|||
[DependsOn(typeof(AbpIdentityDomainSharedModule))] |
|||
public class AbpWebhooksIdentityModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpWebhooksIdentityModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<IdentityResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/Webhooks/Identity/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
[Serializable] |
|||
public class IdentityRoleNameChangedWto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public string OldName { get; set; } |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
public class IdentityRoleWebhooker : |
|||
IDistributedEventHandler<EntityCreatedEto<IdentityRoleEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<IdentityRoleEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<IdentityRoleEto>>, |
|||
IDistributedEventHandler<IdentityRoleNameChangedEto>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IWebhookPublisher _webhookPublisher; |
|||
|
|||
public IdentityRoleWebhooker( |
|||
IWebhookPublisher webhookPublisher) |
|||
{ |
|||
_webhookPublisher = webhookPublisher; |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<IdentityRoleEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityRole.Create, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityUpdatedEto<IdentityRoleEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityRole.Update, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<IdentityRoleEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityRole.Delete, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(IdentityRoleNameChangedEto eventData) |
|||
{ |
|||
await _webhookPublisher.PublishAsync( |
|||
IdentityWebhookNames.IdentityRole.ChangeName, |
|||
new IdentityRoleNameChangedWto |
|||
{ |
|||
Id = eventData.Id, |
|||
Name = eventData.Name, |
|||
OldName = eventData.OldName, |
|||
}, |
|||
eventData.TenantId); |
|||
} |
|||
|
|||
protected async virtual Task PublishAsync(string webhookName, IdentityRoleEto eto) |
|||
{ |
|||
await _webhookPublisher.PublishAsync( |
|||
webhookName, |
|||
new IdentityRoleWto |
|||
{ |
|||
Id = eto.Id, |
|||
Name = eto.Name |
|||
}, |
|||
eto.TenantId); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
[Serializable] |
|||
public class IdentityRoleWto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
public class IdentityUserWebhooker : |
|||
IDistributedEventHandler<EntityCreatedEto<UserEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<UserEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<UserEto>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IWebhookPublisher _webhookPublisher; |
|||
|
|||
public IdentityUserWebhooker( |
|||
IWebhookPublisher webhookPublisher) |
|||
{ |
|||
_webhookPublisher = webhookPublisher; |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<UserEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityUser.Create, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityUpdatedEto<UserEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityUser.Update, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<UserEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityUser.Delete, eventData.Entity); |
|||
} |
|||
|
|||
protected async virtual Task PublishAsync(string webhookName, UserEto eto) |
|||
{ |
|||
await _webhookPublisher.PublishAsync( |
|||
webhookName, |
|||
new IdentityUserWto |
|||
{ |
|||
Id = eto.Id, |
|||
Name = eto.Name, |
|||
Email = eto.Email, |
|||
EmailConfirmed = eto.EmailConfirmed, |
|||
UserName = eto.UserName, |
|||
PhoneNumber = eto.PhoneNumber, |
|||
PhoneNumberConfirmed = eto.PhoneNumberConfirmed, |
|||
Surname = eto.Surname, |
|||
}, |
|||
eto.TenantId); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
[Serializable] |
|||
public class IdentityUserWto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string UserName { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public string Surname { get; set; } |
|||
|
|||
public string Email { get; set; } |
|||
|
|||
public bool EmailConfirmed { get; set; } |
|||
|
|||
public string PhoneNumber { get; set; } |
|||
|
|||
public bool PhoneNumberConfirmed { get; set; } |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
using Volo.Abp.Identity.Localization; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
public class IdentityWebhookDefinitionProvider : WebhookDefinitionProvider |
|||
{ |
|||
public override void Define(IWebhookDefinitionContext context) |
|||
{ |
|||
var identityGroup = context.AddGroup( |
|||
IdentityWebhookNames.GroupName, |
|||
L("Webhooks:Identity")); |
|||
|
|||
identityGroup.AddWebhooks(CreateIdentityRoleWebhooks()); |
|||
identityGroup.AddWebhooks(CreateIdentityUserWebhooks()); |
|||
} |
|||
|
|||
protected virtual WebhookDefinition[] CreateIdentityRoleWebhooks() |
|||
{ |
|||
return new[] |
|||
{ |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityRole.Create, |
|||
L("Webhooks:CreateRole"), |
|||
L("Webhooks:CreateRoleDesc")), |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityRole.Update, |
|||
L("Webhooks:UpdateRole"), |
|||
L("Webhooks:UpdateRoleDesc")), |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityRole.Delete, |
|||
L("Webhooks:DeleteRole"), |
|||
L("Webhooks:DeleteRoleDesc")), |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityRole.ChangeName, |
|||
L("Webhooks:ChangeRoleName"), |
|||
L("Webhooks:ChangeRoleNameDesc")), |
|||
}; |
|||
} |
|||
|
|||
protected virtual WebhookDefinition[] CreateIdentityUserWebhooks() |
|||
{ |
|||
return new[] |
|||
{ |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityUser.Create, |
|||
L("Webhooks:CreateUser"), |
|||
L("Webhooks:CreateUserDesc")), |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityUser.Update, |
|||
L("Webhooks:UpdateUser"), |
|||
L("Webhooks:UpdateUserDesc")), |
|||
new WebhookDefinition( |
|||
IdentityWebhookNames.IdentityUser.Delete, |
|||
L("Webhooks:DeleteUser"), |
|||
L("Webhooks:DeleteUserDesc")), |
|||
}; |
|||
} |
|||
|
|||
private static ILocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<IdentityResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
public static class IdentityWebhookNames |
|||
{ |
|||
public const string GroupName = "abp.webhooks.identity"; |
|||
public static class IdentityRole |
|||
{ |
|||
public const string Prefix = GroupName + ".roles"; |
|||
public const string Create = Prefix + ".create"; |
|||
public const string Update = Prefix + ".update"; |
|||
public const string Delete = Prefix + ".delete"; |
|||
public const string ChangeName = Prefix + ".change_name"; |
|||
} |
|||
|
|||
public static class IdentityUser |
|||
{ |
|||
public const string Prefix = GroupName + ".users"; |
|||
public const string Create = Prefix + ".create"; |
|||
public const string Update = Prefix + ".update"; |
|||
public const string Delete = Prefix + ".delete"; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Webhooks:Identity": "Identity", |
|||
"Webhooks:CreateRole": "Create Role", |
|||
"Webhooks:CreateRoleDesc": "A new role has been created", |
|||
"Webhooks:UpdateRole": "Update Role", |
|||
"Webhooks:UpdateRoleDesc": "A role has changed", |
|||
"Webhooks:DeleteRole": "Delete Role", |
|||
"Webhooks:DeleteRoleDesc": "Deleted Role", |
|||
"Webhooks:ChangeRoleName": "Change Role Name", |
|||
"Webhooks:ChangeRoleNameDesc": "The name of a role was changed", |
|||
"Webhooks:CreateUser": "Create User", |
|||
"Webhooks:CreateUserDesc": "A new user has been created", |
|||
"Webhooks:UpdateUser": "Update User", |
|||
"Webhooks:UpdateUserDesc": "A user has been changed", |
|||
"Webhooks:DeleteUser": "Delete User", |
|||
"Webhooks:DeleteUserDesc": "Deleted User" |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Webhooks:Identity": "身份认证", |
|||
"Webhooks:CreateRole": "创建角色", |
|||
"Webhooks:CreateRoleDesc": "一个新角色已创建", |
|||
"Webhooks:UpdateRole": "编辑角色", |
|||
"Webhooks:UpdateRoleDesc": "一个角色属性已变更", |
|||
"Webhooks:DeleteRole": "删除角色", |
|||
"Webhooks:DeleteRoleDesc": "已删除角色", |
|||
"Webhooks:ChangeRoleName": "改变角色名称", |
|||
"Webhooks:ChangeRoleNameDesc": "一个角色的名称被更改", |
|||
"Webhooks:CreateUser": "创建用户", |
|||
"Webhooks:CreateUserDesc": "一个新用户已创建", |
|||
"Webhooks:UpdateUser": "编辑用户", |
|||
"Webhooks:UpdateUserDesc": "一个用户属性已变更", |
|||
"Webhooks:DeleteUser": "删除用户", |
|||
"Webhooks:DeleteUserDesc": "已删除用户" |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
public class OrganizationUnitWebhooker : |
|||
IDistributedEventHandler<EntityCreatedEto<OrganizationUnitEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<OrganizationUnitEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<OrganizationUnitEto>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IWebhookPublisher _webhookPublisher; |
|||
|
|||
public OrganizationUnitWebhooker( |
|||
IWebhookPublisher webhookPublisher) |
|||
{ |
|||
_webhookPublisher = webhookPublisher; |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<OrganizationUnitEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityRole.Create, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityUpdatedEto<OrganizationUnitEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityRole.Update, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<OrganizationUnitEto> eventData) |
|||
{ |
|||
await PublishAsync(IdentityWebhookNames.IdentityRole.Delete, eventData.Entity); |
|||
} |
|||
|
|||
protected async virtual Task PublishAsync(string webhookName, OrganizationUnitEto eto) |
|||
{ |
|||
await _webhookPublisher.PublishAsync( |
|||
webhookName, |
|||
new OrganizationUnitWto |
|||
{ |
|||
Id = eto.Id, |
|||
DisplayName = eto.DisplayName |
|||
}, |
|||
eto.TenantId); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Identity; |
|||
|
|||
[Serializable] |
|||
public class OrganizationUnitWto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Code { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,30 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="LINGYUN\Abp\Webhooks\Saas\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\Webhooks\Saas\Localization\Resources\en.json" /> |
|||
<None Remove="LINGYUN\Abp\Webhooks\Saas\Localization\Resources\zh-Hans.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.EventBus" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Webhooks\LINGYUN.Abp.Webhooks.csproj" /> |
|||
<ProjectReference Include="..\..\saas\LINGYUN.Abp.Saas.Domain.Shared\LINGYUN.Abp.Saas.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,30 @@ |
|||
using LINGYUN.Abp.Saas; |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
[DependsOn(typeof(AbpDddDomainModule))] |
|||
[DependsOn(typeof(AbpEventBusModule))] |
|||
[DependsOn(typeof(AbpSaasDomainSharedModule))] |
|||
public class AbpWebhooksSaasModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpWebhooksSaasModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AbpSaasResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/Webhooks/Saas/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
public class EditionWebhooker : |
|||
IDistributedEventHandler<EntityCreatedEto<EditionEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<EditionEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<EditionEto>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IWebhookPublisher _webhookPublisher; |
|||
|
|||
public EditionWebhooker( |
|||
IWebhookPublisher webhookPublisher) |
|||
{ |
|||
_webhookPublisher = webhookPublisher; |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<EditionEto> eventData) |
|||
{ |
|||
await PublishAsync(SaasWebhookNames.Edition.Create, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityUpdatedEto<EditionEto> eventData) |
|||
{ |
|||
await PublishAsync(SaasWebhookNames.Edition.Update, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<EditionEto> eventData) |
|||
{ |
|||
await PublishAsync(SaasWebhookNames.Edition.Delete, eventData.Entity); |
|||
} |
|||
|
|||
protected async virtual Task PublishAsync(string webhookName, EditionEto eto) |
|||
{ |
|||
await _webhookPublisher.PublishAsync( |
|||
webhookName, |
|||
new EditionWto |
|||
{ |
|||
Id = eto.Id, |
|||
DisplayName = eto.DisplayName |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
[Serializable] |
|||
public class EditionWto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Webhooks:Saas": "Saas", |
|||
"Webhooks:CreateEdition": "Create Edition", |
|||
"Webhooks:CreateEditionDesc": "A new Edition has been created", |
|||
"Webhooks:UpdateEdition": "Update Edition", |
|||
"Webhooks:UpdateEditionDesc": "A Edition has changed", |
|||
"Webhooks:DeleteEdition": "Delete Edition", |
|||
"Webhooks:DeleteEditionDesc": "Deleted Edition", |
|||
"Webhooks:CreateTenant": "Create Tenant", |
|||
"Webhooks:CreateTenantDesc": "A new Tenant has been created", |
|||
"Webhooks:UpdateTenant": "Update Tenant", |
|||
"Webhooks:UpdateTenantDesc": "A Tenant has been changed", |
|||
"Webhooks:DeleteTenant": "Delete Tenant", |
|||
"Webhooks:DeleteTenantDesc": "Deleted Tenant" |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Webhooks:Saas": "Saas", |
|||
"Webhooks:CreateEdition": "创建版本", |
|||
"Webhooks:CreateEditionDesc": "一个新版本已创建", |
|||
"Webhooks:UpdateEdition": "编辑版本", |
|||
"Webhooks:UpdateEditionDesc": "一个版本属性已变更", |
|||
"Webhooks:DeleteEdition": "删除版本", |
|||
"Webhooks:DeleteEditionDesc": "已删除版本", |
|||
"Webhooks:CreateTenant": "创建租户", |
|||
"Webhooks:CreateTenantDesc": "一个新租户已创建", |
|||
"Webhooks:UpdateTenant": "编辑租户", |
|||
"Webhooks:UpdateTenantDesc": "一个租户属性已变更", |
|||
"Webhooks:DeleteTenant": "删除租户", |
|||
"Webhooks:DeleteTenantDesc": "已删除租户" |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
public class SaasWebhookDefinitionProvider : WebhookDefinitionProvider |
|||
{ |
|||
public override void Define(IWebhookDefinitionContext context) |
|||
{ |
|||
var saasGroup = context.AddGroup( |
|||
SaasWebhookNames.GroupName, |
|||
L("Webhooks:Saas")); |
|||
|
|||
saasGroup.AddWebhooks(CreateEditionWebhooks()); |
|||
saasGroup.AddWebhooks(CreateTenantWebhooks()); |
|||
} |
|||
|
|||
protected virtual WebhookDefinition[] CreateEditionWebhooks() |
|||
{ |
|||
return new[] |
|||
{ |
|||
new WebhookDefinition( |
|||
SaasWebhookNames.Edition.Create, |
|||
L("Webhooks:CreateEdition"), |
|||
L("Webhooks:CreateEditionDesc")), |
|||
new WebhookDefinition( |
|||
SaasWebhookNames.Edition.Update, |
|||
L("Webhooks:UpdateEdition"), |
|||
L("Webhooks:UpdateEditionDesc")), |
|||
new WebhookDefinition( |
|||
SaasWebhookNames.Edition.Delete, |
|||
L("Webhooks:DeleteEdition"), |
|||
L("Webhooks:DeleteEditionDesc")), |
|||
}; |
|||
} |
|||
|
|||
protected virtual WebhookDefinition[] CreateTenantWebhooks() |
|||
{ |
|||
return new[] |
|||
{ |
|||
new WebhookDefinition( |
|||
SaasWebhookNames.Tenant.Create, |
|||
L("Webhooks:CreateTenant"), |
|||
L("Webhooks:CreateTenantDesc")), |
|||
new WebhookDefinition( |
|||
SaasWebhookNames.Tenant.Update, |
|||
L("Webhooks:UpdateTenant"), |
|||
L("Webhooks:UpdateTenantDesc")), |
|||
new WebhookDefinition( |
|||
SaasWebhookNames.Tenant.Delete, |
|||
L("Webhooks:DeleteTenant"), |
|||
L("Webhooks:DeleteTenantDesc")), |
|||
}; |
|||
} |
|||
|
|||
private static ILocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<AbpSaasResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
public static class SaasWebhookNames |
|||
{ |
|||
public const string GroupName = "abp.webhooks.saas"; |
|||
public static class Edition |
|||
{ |
|||
public const string Prefix = GroupName + ".editions"; |
|||
public const string Create = Prefix + ".create"; |
|||
public const string Update = Prefix + ".update"; |
|||
public const string Delete = Prefix + ".delete"; |
|||
} |
|||
|
|||
public static class Tenant |
|||
{ |
|||
public const string Prefix = GroupName + ".tenants"; |
|||
public const string Create = Prefix + ".create"; |
|||
public const string Update = Prefix + ".update"; |
|||
public const string Delete = Prefix + ".delete"; |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
public class TenantWebhooker : |
|||
IDistributedEventHandler<EntityCreatedEto<TenantEto>>, |
|||
IDistributedEventHandler<EntityUpdatedEto<TenantEto>>, |
|||
IDistributedEventHandler<EntityDeletedEto<TenantEto>>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IWebhookPublisher _webhookPublisher; |
|||
|
|||
public TenantWebhooker( |
|||
IWebhookPublisher webhookPublisher) |
|||
{ |
|||
_webhookPublisher = webhookPublisher; |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityCreatedEto<TenantEto> eventData) |
|||
{ |
|||
await PublishAsync(SaasWebhookNames.Tenant.Create, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityUpdatedEto<TenantEto> eventData) |
|||
{ |
|||
await PublishAsync(SaasWebhookNames.Tenant.Update, eventData.Entity); |
|||
} |
|||
|
|||
public async virtual Task HandleEventAsync(EntityDeletedEto<TenantEto> eventData) |
|||
{ |
|||
await PublishAsync(SaasWebhookNames.Tenant.Delete, eventData.Entity); |
|||
} |
|||
|
|||
protected async virtual Task PublishAsync(string webhookName, TenantEto eto) |
|||
{ |
|||
await _webhookPublisher.PublishAsync( |
|||
webhookName, |
|||
new TenantWto |
|||
{ |
|||
Id = eto.Id, |
|||
Name = eto.Name |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Webhooks.Saas; |
|||
|
|||
[Serializable] |
|||
public class TenantWto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Authorization" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Features" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Domain.Shared\LINGYUN.Abp.WebhooksManagement.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,62 @@ |
|||
using LINGYUN.Abp.WebhooksManagement.Localization; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement.Authorization; |
|||
|
|||
public class WebhooksManagementPermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var group = context.AddGroup( |
|||
WebhooksManagementPermissions.GroupName, |
|||
L("Permission:WebhooksManagement"), |
|||
MultiTenancySides.Host); |
|||
|
|||
var subscription = group.AddPermission( |
|||
WebhooksManagementPermissions.WebhookSubscription.Default, |
|||
L("Permission:Subscriptions"), |
|||
MultiTenancySides.Host) |
|||
.WithProviders(ClientPermissionValueProvider.ProviderName); |
|||
subscription.AddChild( |
|||
WebhooksManagementPermissions.WebhookSubscription.Create, |
|||
L("Permission:Create"), |
|||
MultiTenancySides.Host) |
|||
.WithProviders(ClientPermissionValueProvider.ProviderName); |
|||
subscription.AddChild( |
|||
WebhooksManagementPermissions.WebhookSubscription.Update, |
|||
L("Permission:Update"), |
|||
MultiTenancySides.Host) |
|||
.WithProviders(ClientPermissionValueProvider.ProviderName); |
|||
subscription.AddChild( |
|||
WebhooksManagementPermissions.WebhookSubscription.Delete, |
|||
L("Permission:Delete"), |
|||
MultiTenancySides.Host) |
|||
.WithProviders(ClientPermissionValueProvider.ProviderName); |
|||
|
|||
var sendAttempts = group.AddPermission( |
|||
WebhooksManagementPermissions.WebhooksSendAttempts.Default, |
|||
L("Permission:SendAttempts"), |
|||
MultiTenancySides.Host); |
|||
sendAttempts.AddChild( |
|||
WebhooksManagementPermissions.WebhooksSendAttempts.Resend, |
|||
L("Permission:Resend"), |
|||
MultiTenancySides.Host); |
|||
|
|||
group.AddPermission( |
|||
WebhooksManagementPermissions.Publish, |
|||
L("Permission:Publish")) |
|||
.WithProviders(ClientPermissionValueProvider.ProviderName); |
|||
|
|||
group.AddPermission( |
|||
WebhooksManagementPermissions.ManageSettings, |
|||
L("Permission:ManageSettings"), |
|||
MultiTenancySides.Host); |
|||
} |
|||
|
|||
private static LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<WebhooksManagementResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
namespace LINGYUN.Abp.WebhooksManagement.Authorization; |
|||
|
|||
public static class WebhooksManagementPermissions |
|||
{ |
|||
public const string GroupName = "AbpWebhooks"; |
|||
|
|||
/// <summary>
|
|||
/// 授权允许发布Webhooks事件, 建议客户端授权
|
|||
/// </summary>
|
|||
public const string Publish = GroupName + ".Publish"; |
|||
|
|||
public const string ManageSettings = GroupName + ".ManageSettings"; |
|||
|
|||
public static class WebhookSubscription |
|||
{ |
|||
public const string Default = GroupName + ".Subscriptions"; |
|||
public const string Create = Default + ".Create"; |
|||
public const string Update = Default + ".Update"; |
|||
public const string Delete = Default + ".Delete"; |
|||
} |
|||
|
|||
public static class WebhooksSendAttempts |
|||
{ |
|||
public const string Default = GroupName + ".SendAttempts"; |
|||
public const string Resend = Default + ".Resend"; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using LINGYUN.Abp.WebhooksManagement.Localization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement.Features; |
|||
|
|||
public class WebhooksManagementFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
//var group = context.AddGroup(WebhooksManagementFeatureNames.GroupName, L("Features:WebhooksManagement"));
|
|||
} |
|||
|
|||
private static ILocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<WebhooksManagementResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace LINGYUN.Abp.WebhooksManagement.Features; |
|||
|
|||
public static class WebhooksManagementFeatureNames |
|||
{ |
|||
public const string GroupName = "WebhooksManagement"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public interface IWebhookPublishAppService : IApplicationService |
|||
{ |
|||
Task PublishAsync(WebhookPublishInput input); |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public interface IWebhookSendRecordAppService : IApplicationService |
|||
{ |
|||
Task<WebhookSendRecordDto> GetAsync(Guid id); |
|||
|
|||
Task ResendAsync(Guid id); |
|||
|
|||
Task<PagedResultDto<WebhookSendRecordDto>> GetListAsync(WebhookSendRecordGetListInput input); |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public interface IWebhookSubscriptionAppService : |
|||
ICrudAppService< |
|||
WebhookSubscriptionDto, |
|||
Guid, |
|||
WebhookSubscriptionGetListInput, |
|||
WebhookSubscriptionCreateInput, |
|||
WebhookSubscriptionUpdateInput> |
|||
{ |
|||
Task<ListResultDto<WebhookAvailableGroupDto>> GetAllAvailableWebhooksAsync(); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookAvailableDto |
|||
{ |
|||
public string Name { get; set; } |
|||
public string DisplayName { get; set; } |
|||
public string Description { get; set; } |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookAvailableGroupDto |
|||
{ |
|||
public string Name { get; set; } |
|||
public string DisplayName { get; set; } |
|||
public List<WebhookAvailableDto> Webhooks { get; set; } = new List<WebhookAvailableDto>(); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookEventRecordDto : EntityDto<Guid> |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
public string WebhookName { get; set; } |
|||
public string Data { get; set; } |
|||
public DateTime CreationTime { get; set; } |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookPublishInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(WebhookEventRecordConsts), nameof(WebhookEventRecordConsts.MaxWebhookNameLength))] |
|||
public string WebhookName { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(WebhookEventRecordConsts), nameof(WebhookEventRecordConsts.MaxDataLength))] |
|||
public string Data { get; set; } |
|||
|
|||
public bool SendExactSameData { get; set; } |
|||
|
|||
public WebhooksHeaderInput Header { get; set; } = new WebhooksHeaderInput(); |
|||
|
|||
public List<Guid?> TenantIds { get; set; } = new List<Guid?>(); |
|||
} |
|||
|
|||
public class WebhooksHeaderInput |
|||
{ |
|||
public bool UseOnlyGivenHeaders { get; set; } |
|||
|
|||
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(); |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.Net; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookSendRecordDto : EntityDto<Guid> |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public Guid WebhookEventId { get; set; } |
|||
|
|||
public Guid WebhookSubscriptionId { get; set; } |
|||
|
|||
public string Response { get; set; } |
|||
|
|||
public HttpStatusCode? ResponseStatusCode { get; set; } |
|||
|
|||
public DateTime CreationTime { get; set; } |
|||
|
|||
public DateTime? LastModificationTime { get; set; } |
|||
|
|||
public WebhookEventRecordDto WebhookEvent { get; set; } = new WebhookEventRecordDto(); |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Net; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookSendRecordGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
|
|||
public Guid? WebhookEventId { get; set; } |
|||
|
|||
public Guid? SubscriptionId { get; set; } |
|||
|
|||
public HttpStatusCode? ResponseStatusCode { get; set; } |
|||
|
|||
public DateTime? BeginCreationTime { get; set; } |
|||
|
|||
public DateTime? EndCreationTime { get; set; } |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookSubscriptionCreateInput : WebhookSubscriptionCreateOrUpdateInput |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class WebhookSubscriptionUpdateInput : WebhookSubscriptionCreateOrUpdateInput |
|||
{ |
|||
|
|||
} |
|||
|
|||
public abstract class WebhookSubscriptionCreateOrUpdateInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxWebhookUriLength))] |
|||
public string WebhookUri { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxSecretLength))] |
|||
public string Secret { get; set; } |
|||
|
|||
public bool IsActive { get; set; } |
|||
|
|||
public List<string> Webhooks { get; set; } = new List<string>(); |
|||
|
|||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(); |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookSubscriptionDto : CreationAuditedEntityDto<Guid> |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
public string WebhookUri { get; set; } |
|||
public string Secret { get; set; } |
|||
public bool IsActive { get; set; } |
|||
public List<string> Webhooks { get; set; } = new List<string>(); |
|||
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(); |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhookSubscriptionGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
[DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxWebhookUriLength))] |
|||
public string WebhookUri { get; set; } |
|||
|
|||
[DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxSecretLength))] |
|||
public string Secret { get; set; } |
|||
|
|||
public bool? IsActive { get; set; } |
|||
|
|||
public string Webhooks { get; set; } |
|||
|
|||
public DateTime? BeginCreationTime { get; set; } |
|||
|
|||
public DateTime? EndCreationTime { get; set; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Authorization; |
|||
using Volo.Abp.Features; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpFeaturesModule), |
|||
typeof(AbpAuthorizationModule), |
|||
typeof(AbpDddApplicationContractsModule), |
|||
typeof(WebhooksManagementDomainSharedModule))] |
|||
public class WebhooksManagementApplicationContractsModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public static class WebhooksManagementRemoteServiceConsts |
|||
{ |
|||
public const string RemoteServiceName = "WebhooksManagement"; |
|||
public const string ModuleName = "webhooks-management"; |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Application.Contracts\LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Domain\LINGYUN.Abp.WebhooksManagement.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,44 @@ |
|||
using Newtonsoft.Json; |
|||
using System.Linq; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement.Extensions |
|||
{ |
|||
public static class WebhookSubscriptionExtensions |
|||
{ |
|||
public static WebhookSubscriptionDto ToWebhookSubscriptionDto(this WebhookSubscription webhookSubscription) |
|||
{ |
|||
return new WebhookSubscriptionDto |
|||
{ |
|||
Id = webhookSubscription.Id, |
|||
TenantId = webhookSubscription.TenantId, |
|||
IsActive = webhookSubscription.IsActive, |
|||
Secret = webhookSubscription.Secret, |
|||
WebhookUri = webhookSubscription.WebhookUri, |
|||
Webhooks = webhookSubscription.GetSubscribedWebhooks(), |
|||
Headers = webhookSubscription.GetWebhookHeaders(), |
|||
CreationTime = webhookSubscription.CreationTime, |
|||
CreatorId = webhookSubscription.CreatorId |
|||
}; |
|||
} |
|||
|
|||
public static string ToSubscribedWebhooksString(this WebhookSubscriptionUpdateInput webhookSubscription) |
|||
{ |
|||
if (webhookSubscription.Webhooks.Any()) |
|||
{ |
|||
return JsonConvert.SerializeObject(webhookSubscription.Webhooks); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static string ToWebhookHeadersString(this WebhookSubscriptionUpdateInput webhookSubscription) |
|||
{ |
|||
if (webhookSubscription.Headers.Any()) |
|||
{ |
|||
return JsonConvert.SerializeObject(webhookSubscription.Headers); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using LINGYUN.Abp.Webhooks; |
|||
using LINGYUN.Abp.WebhooksManagement.Authorization; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Newtonsoft.Json; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
[Authorize(WebhooksManagementPermissions.Publish)] |
|||
public class WebhookPublishAppService : WebhooksManagementAppServiceBase, IWebhookPublishAppService |
|||
{ |
|||
protected IWebhookPublisher InnerPublisher { get; } |
|||
|
|||
public WebhookPublishAppService(IWebhookPublisher innerPublisher) |
|||
{ |
|||
InnerPublisher = innerPublisher; |
|||
} |
|||
|
|||
public async virtual Task PublishAsync(WebhookPublishInput input) |
|||
{ |
|||
var webhookHeader = new WebhookHeader |
|||
{ |
|||
UseOnlyGivenHeaders = input.Header.UseOnlyGivenHeaders, |
|||
Headers = input.Header.Headers, |
|||
}; |
|||
var inputData = JsonConvert.DeserializeObject(input.Data); |
|||
|
|||
if (input.TenantIds.Any()) |
|||
{ |
|||
await InnerPublisher.PublishAsync( |
|||
input.TenantIds.ToArray(), |
|||
input.WebhookName, |
|||
inputData, |
|||
input.SendExactSameData, |
|||
webhookHeader); |
|||
return; |
|||
} |
|||
await InnerPublisher.PublishAsync( |
|||
input.WebhookName, |
|||
inputData, |
|||
input.SendExactSameData, |
|||
webhookHeader); |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using LINGYUN.Abp.Webhooks; |
|||
using LINGYUN.Abp.WebhooksManagement.Authorization; |
|||
using LINGYUN.Abp.WebhooksManagement.Extensions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.BackgroundJobs; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
[Authorize(WebhooksManagementPermissions.WebhooksSendAttempts.Default)] |
|||
public class WebhookSendRecordAppService : WebhooksManagementAppServiceBase, IWebhookSendRecordAppService |
|||
{ |
|||
protected IBackgroundJobManager BackgroundJobManager => LazyServiceProvider.LazyGetRequiredService<IBackgroundJobManager>(); |
|||
protected IWebhookEventRecordRepository EventRepository => LazyServiceProvider.LazyGetRequiredService<IWebhookEventRecordRepository>(); |
|||
protected IWebhookSubscriptionRepository SubscriptionRepository => LazyServiceProvider.LazyGetRequiredService<IWebhookSubscriptionRepository>(); |
|||
|
|||
|
|||
protected IWebhookSendRecordRepository RecordRepository { get; } |
|||
|
|||
public WebhookSendRecordAppService( |
|||
IWebhookSendRecordRepository recordRepository) |
|||
{ |
|||
RecordRepository = recordRepository; |
|||
} |
|||
|
|||
public async virtual Task<WebhookSendRecordDto> GetAsync(Guid id) |
|||
{ |
|||
var sendRecord = await RecordRepository.GetAsync(id); |
|||
|
|||
return ObjectMapper.Map<WebhookSendRecord, WebhookSendRecordDto>(sendRecord); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<WebhookSendRecordDto>> GetListAsync(WebhookSendRecordGetListInput input) |
|||
{ |
|||
var filter = new WebhookSendRecordFilter |
|||
{ |
|||
SubscriptionId = input.SubscriptionId, |
|||
ResponseStatusCode = input.ResponseStatusCode, |
|||
BeginCreationTime = input.BeginCreationTime, |
|||
EndCreationTime = input.EndCreationTime, |
|||
WebhookEventId = input.WebhookEventId, |
|||
Filter = input.Filter |
|||
}; |
|||
var totalCount = await RecordRepository.GetCountAsync(filter); |
|||
var sendRecords = await RecordRepository.GetListAsync(filter, |
|||
input.Sorting, input.MaxResultCount, input.SkipCount); |
|||
|
|||
return new PagedResultDto<WebhookSendRecordDto>(totalCount, |
|||
ObjectMapper.Map<List<WebhookSendRecord>, List<WebhookSendRecordDto>>(sendRecords)); |
|||
} |
|||
|
|||
[Authorize(WebhooksManagementPermissions.WebhooksSendAttempts.Resend)] |
|||
public async virtual Task ResendAsync(Guid id) |
|||
{ |
|||
var sendRecord = await RecordRepository.GetAsync(id); |
|||
var sendEvent = await EventRepository.GetAsync(sendRecord.WebhookEventId); |
|||
var subscription = await SubscriptionRepository.GetAsync(sendRecord.WebhookSubscriptionId); |
|||
|
|||
await BackgroundJobManager.EnqueueAsync(new WebhookSenderArgs |
|||
{ |
|||
TenantId = CurrentTenant.Id, |
|||
WebhookSubscriptionId = sendRecord.WebhookSubscriptionId, |
|||
WebhookEventId = sendRecord.WebhookEventId, |
|||
WebhookName = sendEvent.WebhookName, |
|||
WebhookUri = subscription.WebhookUri, |
|||
Data = sendEvent.Data, |
|||
Headers = subscription.GetWebhookHeaders(), |
|||
Secret = subscription.Secret, |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
using LINGYUN.Abp.Webhooks; |
|||
using LINGYUN.Abp.WebhooksManagement.Authorization; |
|||
using LINGYUN.Abp.WebhooksManagement.Extensions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
[Authorize(WebhooksManagementPermissions.WebhookSubscription.Default)] |
|||
public class WebhookSubscriptionAppService : WebhooksManagementAppServiceBase, IWebhookSubscriptionAppService |
|||
{ |
|||
protected IWebhookDefinitionManager WebhookDefinitionManager { get; } |
|||
protected IWebhookSubscriptionRepository SubscriptionRepository { get; } |
|||
|
|||
public WebhookSubscriptionAppService( |
|||
IWebhookDefinitionManager webhookDefinitionManager, |
|||
IWebhookSubscriptionRepository subscriptionRepository) |
|||
{ |
|||
WebhookDefinitionManager = webhookDefinitionManager; |
|||
SubscriptionRepository = subscriptionRepository; |
|||
} |
|||
|
|||
[Authorize(WebhooksManagementPermissions.WebhookSubscription.Create)] |
|||
public async virtual Task<WebhookSubscriptionDto> CreateAsync(WebhookSubscriptionCreateInput input) |
|||
{ |
|||
await CheckSubscribedAsync(input); |
|||
|
|||
var subscription = new WebhookSubscription( |
|||
GuidGenerator.Create(), |
|||
input.WebhookUri, |
|||
input.Secret, |
|||
JsonConvert.SerializeObject(input.Webhooks), |
|||
JsonConvert.SerializeObject(input.Headers), |
|||
CurrentTenant.Id); |
|||
|
|||
await SubscriptionRepository.InsertAsync(subscription); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return subscription.ToWebhookSubscriptionDto(); |
|||
} |
|||
|
|||
[Authorize(WebhooksManagementPermissions.WebhookSubscription.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return SubscriptionRepository.DeleteAsync(id); |
|||
} |
|||
|
|||
public async virtual Task<WebhookSubscriptionDto> GetAsync(Guid id) |
|||
{ |
|||
var subscription = await SubscriptionRepository.GetAsync(id); |
|||
|
|||
return subscription.ToWebhookSubscriptionDto(); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<WebhookSubscriptionDto>> GetListAsync(WebhookSubscriptionGetListInput input) |
|||
{ |
|||
var filter = new WebhookSubscriptionFilter |
|||
{ |
|||
Filter = input.Filter, |
|||
BeginCreationTime = input.BeginCreationTime, |
|||
EndCreationTime = input.EndCreationTime, |
|||
IsActive = input.IsActive, |
|||
Secret = input.Secret, |
|||
TenantId = input.TenantId, |
|||
Webhooks = input.Webhooks, |
|||
WebhookUri = input.WebhookUri |
|||
}; |
|||
|
|||
var totalCount = await SubscriptionRepository.GetCountAsync(filter); |
|||
var subscriptions = await SubscriptionRepository.GetListAsync(filter, |
|||
input.Sorting, input.MaxResultCount, input.SkipCount); |
|||
|
|||
return new PagedResultDto<WebhookSubscriptionDto>(totalCount, |
|||
subscriptions.Select(subscription => subscription.ToWebhookSubscriptionDto()).ToList()); |
|||
} |
|||
|
|||
[Authorize(WebhooksManagementPermissions.WebhookSubscription.Update)] |
|||
public async virtual Task<WebhookSubscriptionDto> UpdateAsync(Guid id, WebhookSubscriptionUpdateInput input) |
|||
{ |
|||
var subscription = await SubscriptionRepository.GetAsync(id); |
|||
if (!string.Equals(subscription.WebhookUri, input.WebhookUri)) |
|||
{ |
|||
await CheckSubscribedAsync(input); |
|||
} |
|||
|
|||
subscription.SetSecret(input.Secret); |
|||
subscription.SetWebhookUri(input.WebhookUri); |
|||
subscription.SetWebhooks(input.ToSubscribedWebhooksString()); |
|||
subscription.SetHeaders(input.ToWebhookHeadersString()); |
|||
subscription.IsActive = input.IsActive; |
|||
|
|||
await SubscriptionRepository.UpdateAsync(subscription); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return subscription.ToWebhookSubscriptionDto(); |
|||
} |
|||
|
|||
public async virtual Task<ListResultDto<WebhookAvailableGroupDto>> GetAllAvailableWebhooksAsync() |
|||
{ |
|||
var groups = WebhookDefinitionManager.GetGroups(); |
|||
var definitions = new List<WebhookAvailableGroupDto>(); |
|||
|
|||
foreach (var groupDefinition in groups) |
|||
{ |
|||
var group = new WebhookAvailableGroupDto |
|||
{ |
|||
Name = groupDefinition.Name, |
|||
DisplayName = groupDefinition.DisplayName?.Localize(StringLocalizerFactory), |
|||
}; |
|||
|
|||
foreach (var webhookDefinition in groupDefinition.Webhooks.OrderBy(d => d.Name)) |
|||
{ |
|||
if (await WebhookDefinitionManager.IsAvailableAsync(CurrentTenant.Id, webhookDefinition.Name)) |
|||
{ |
|||
group.Webhooks.Add(new WebhookAvailableDto |
|||
{ |
|||
Name = webhookDefinition.Name, |
|||
Description = webhookDefinition.Description?.Localize(StringLocalizerFactory), |
|||
DisplayName = webhookDefinition.DisplayName?.Localize(StringLocalizerFactory) |
|||
}); |
|||
} |
|||
} |
|||
|
|||
definitions.Add(group); |
|||
} |
|||
|
|||
return new ListResultDto<WebhookAvailableGroupDto>(definitions.OrderBy(d => d.Name).ToList()); |
|||
} |
|||
|
|||
protected async virtual Task CheckSubscribedAsync(WebhookSubscriptionCreateOrUpdateInput input) |
|||
{ |
|||
foreach (var webhookName in input.Webhooks) |
|||
{ |
|||
if (await SubscriptionRepository.IsSubscribedAsync(CurrentTenant.Id, input.WebhookUri, webhookName)) |
|||
{ |
|||
throw new BusinessException(WebhooksManagementErrorCodes.WebhookSubscription.DuplicateSubscribed) |
|||
.WithData(nameof(WebhookSubscription.WebhookUri), input.WebhookUri) |
|||
.WithData(nameof(WebhookSubscription.Webhooks), webhookName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using LINGYUN.Abp.WebhooksManagement.Localization; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public abstract class WebhooksManagementAppServiceBase : ApplicationService |
|||
{ |
|||
protected WebhooksManagementAppServiceBase() |
|||
{ |
|||
LocalizationResource = typeof(WebhooksManagementResource); |
|||
ObjectMapperContext = typeof(WebhooksManagementApplicationModule); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using AutoMapper; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
public class WebhooksManagementApplicationMapperProfile : Profile |
|||
{ |
|||
public WebhooksManagementApplicationMapperProfile() |
|||
{ |
|||
CreateMap<WebhookEventRecord, WebhookEventRecordDto>(); |
|||
CreateMap<WebhookSendRecord, WebhookSendRecordDto>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Authorization; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.WebhooksManagement; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAuthorizationModule), |
|||
typeof(AbpDddApplicationModule), |
|||
typeof(WebhooksManagementDomainModule))] |
|||
public class WebhooksManagementApplicationModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<WebhooksManagementApplicationModule>(); |
|||
|
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddProfile<WebhooksManagementApplicationMapperProfile>(validate: true); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue