120 changed files with 4184 additions and 0 deletions
@ -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.Abstractions" 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,18 @@ |
|||||
|
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(AbpFeaturesModule))] |
||||
|
[DependsOn(typeof(AbpGuidsModule))] |
||||
|
[DependsOn(typeof(AbpHttpClientModule))] |
||||
|
public class AbpWebhooksModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Webhooks; |
||||
|
|
||||
|
public class AbpWebhooksOptions |
||||
|
{ |
||||
|
public TimeSpan TimeoutDuration { get; set; } |
||||
|
|
||||
|
public int MaxSendAttemptCount { get; set; } |
||||
|
|
||||
|
public bool IsAutomaticSubscriptionDeactivationEnabled { get; set; } |
||||
|
|
||||
|
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,125 @@ |
|||||
|
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 IWebhookSubscriptionManager _webhookSubscriptionManager; |
||||
|
private readonly IWebhookSendAttemptStore _webhookSendAttemptStore; |
||||
|
private readonly IWebhookSender _webhookSender; |
||||
|
|
||||
|
private readonly AbpWebhooksOptions _options; |
||||
|
|
||||
|
public WebhookSenderJob( |
||||
|
IUnitOfWorkManager unitOfWorkManager, |
||||
|
IWebhookSubscriptionManager webhookSubscriptionManager, |
||||
|
IWebhookSendAttemptStore webhookSendAttemptStore, |
||||
|
IWebhookSender webhookSender, |
||||
|
IOptions<AbpWebhooksOptions> options) |
||||
|
{ |
||||
|
_unitOfWorkManager = unitOfWorkManager; |
||||
|
_webhookSubscriptionManager = webhookSubscriptionManager; |
||||
|
_webhookSendAttemptStore = webhookSendAttemptStore; |
||||
|
_webhookSender = webhookSender; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public override async Task ExecuteAsync(WebhookSenderArgs args) |
||||
|
{ |
||||
|
if (args.TryOnce) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await SendWebhook(args); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
Logger.LogWarning("An error occured while sending webhook with try once.", e); |
||||
|
// ignored
|
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await SendWebhook(args); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task SendWebhook(WebhookSenderArgs args) |
||||
|
{ |
||||
|
if (args.WebhookEventId == default) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (args.WebhookSubscriptionId == default) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!args.TryOnce) |
||||
|
{ |
||||
|
var sendAttemptCount = await _webhookSendAttemptStore.GetSendAttemptCountAsync( |
||||
|
args.TenantId, |
||||
|
args.WebhookEventId, |
||||
|
args.WebhookSubscriptionId |
||||
|
); |
||||
|
|
||||
|
if (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,125 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Webhooks |
||||
|
{ |
||||
|
public class DefaultWebhookPublisher : IWebhookPublisher |
||||
|
{ |
||||
|
public IWebhookEventStore WebhookEventStore { get; set; } |
||||
|
|
||||
|
private readonly ICurrentTenant _currentTenant; |
||||
|
private readonly IGuidGenerator _guidGenerator; |
||||
|
private readonly IJsonSerializer _jsonSerializer; |
||||
|
private readonly IBackgroundJobManager _backgroundJobManager; |
||||
|
private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; |
||||
|
|
||||
|
public DefaultWebhookPublisher( |
||||
|
IWebhookSubscriptionManager webhookSubscriptionManager, |
||||
|
ICurrentTenant currentTenant, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IBackgroundJobManager backgroundJobManager) |
||||
|
{ |
||||
|
_currentTenant = currentTenant; |
||||
|
_guidGenerator = guidGenerator; |
||||
|
_jsonSerializer = jsonSerializer; |
||||
|
_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); |
||||
|
} |
||||
|
|
||||
|
private 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 |
||||
|
{ |
||||
|
Id = _guidGenerator.Create(), |
||||
|
WebhookName = webhookName, |
||||
|
Data = _jsonSerializer.Serialize(data), |
||||
|
TenantId = tenantId |
||||
|
}; |
||||
|
|
||||
|
await WebhookEventStore.InsertAndGetIdAsync(webhookInfo); |
||||
|
return webhookInfo; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Webhooks |
||||
|
{ |
||||
|
public class DefaultWebhookSender : IWebhookSender |
||||
|
{ |
||||
|
public ILogger<DefaultWebhookSender> Logger { protected get; set; } |
||||
|
|
||||
|
private readonly AbpWebhooksOptions _options; |
||||
|
private readonly IWebhookManager _webhookManager; |
||||
|
|
||||
|
private const string FailedRequestDefaultContent = "Webhook Send Request Failed"; |
||||
|
|
||||
|
public DefaultWebhookSender( |
||||
|
IOptions<AbpWebhooksOptions> options, |
||||
|
IWebhookManager webhookManager) |
||||
|
{ |
||||
|
_options = options.Value; |
||||
|
_webhookManager = webhookManager; |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
using (var client = new HttpClient |
||||
|
{ |
||||
|
Timeout = _options.TimeoutDuration |
||||
|
}) |
||||
|
{ |
||||
|
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,22 @@ |
|||||
|
namespace LINGYUN.Abp.Webhooks |
||||
|
{ |
||||
|
public interface IWebhookDefinitionContext |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Adds the specified webhook definition. Throws exception if it is already added
|
||||
|
/// </summary>
|
||||
|
void Add(params WebhookDefinition[] definitions); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a webhook definition by name.
|
||||
|
/// Returns null if there is no webhook definition with given name.
|
||||
|
/// </summary>
|
||||
|
WebhookDefinition GetOrNull(string name); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Remove webhook with given name
|
||||
|
/// </summary>
|
||||
|
/// <param name="name">webhook definition name</param>
|
||||
|
void Remove(string name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
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>
|
||||
|
/// 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<IReadOnlyCollection<WebhookSendAttempt>> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, |
||||
|
int skipCount) |
||||
|
{ |
||||
|
return Task.FromResult(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,57 @@ |
|||||
|
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>
|
||||
|
/// 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,54 @@ |
|||||
|
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, WebhookDefinition> Webhooks { get; } |
||||
|
|
||||
|
public WebhookDefinitionContext(Dictionary<string, WebhookDefinition> webhooks) |
||||
|
{ |
||||
|
Webhooks = webhooks; |
||||
|
} |
||||
|
|
||||
|
public void Add(params WebhookDefinition[] definitions) |
||||
|
{ |
||||
|
if (definitions.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var definition in definitions) |
||||
|
{ |
||||
|
Webhooks[definition.Name] = definition; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public WebhookDefinition GetOrNull([NotNull] string name) |
||||
|
{ |
||||
|
Check.NotNull(name, nameof(name)); |
||||
|
|
||||
|
if (!Webhooks.ContainsKey(name)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return Webhooks[name]; |
||||
|
} |
||||
|
|
||||
|
public void Remove(string name) |
||||
|
{ |
||||
|
Check.NotNull(name, nameof(name)); |
||||
|
|
||||
|
if (!Webhooks.ContainsKey(name)) |
||||
|
{ |
||||
|
throw new AbpException($"Undefined notification webhook: '{name}'."); |
||||
|
} |
||||
|
|
||||
|
Webhooks.Remove(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
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.DependencyInjection; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Webhooks |
||||
|
{ |
||||
|
internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency |
||||
|
{ |
||||
|
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; |
||||
|
|
||||
|
_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 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>(); |
||||
|
|
||||
|
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,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,84 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
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 IJsonSerializer JsonSerializer { get; } |
||||
|
protected IWebhookSendAttemptStore WebhookSendAttemptStore { get; } |
||||
|
|
||||
|
protected WebhookManager( |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IWebhookSendAttemptStore webhookSendAttemptStore) |
||||
|
{ |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
WebhookSendAttemptStore = webhookSendAttemptStore; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<WebhookPayload> GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs) |
||||
|
{ |
||||
|
var data = JsonSerializer.Serialize(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 = JsonSerializer.Serialize(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,67 @@ |
|||||
|
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>
|
||||
|
/// Tries to send webhook only one time without checking to send attempt count
|
||||
|
/// </summary>
|
||||
|
public bool TryOnce { 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,175 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Authorization; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Webhooks |
||||
|
{ |
||||
|
public class WebhookSubscriptionManager : IWebhookSubscriptionManager |
||||
|
{ |
||||
|
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 |
||||
|
{ |
||||
|
var subscription = await WebhookSubscriptionsStore.GetAsync(webhookSubscription.Id); |
||||
|
subscription.WebhookUri = webhookSubscription.WebhookUri; |
||||
|
subscription.Webhooks = webhookSubscription.Webhooks; |
||||
|
subscription.Headers = webhookSubscription.Headers; |
||||
|
await WebhookSubscriptionsStore.UpdateAsync(subscription); |
||||
|
} |
||||
|
|
||||
|
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,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,22 @@ |
|||||
|
using LINGYUN.Abp.WebhooksManagement.Localization; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.Authorization; |
||||
|
|
||||
|
public class WebhooksManagementPermissionDefinitionProvider : PermissionDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IPermissionDefinitionContext context) |
||||
|
{ |
||||
|
var group = context.AddGroup(WebhooksManagementPermissions.GroupName, L("Permission:WebhooksManagement")); |
||||
|
|
||||
|
group.AddPermission( |
||||
|
WebhooksManagementPermissions.ManageSettings, |
||||
|
L("Permission:ManageSettings")); |
||||
|
} |
||||
|
|
||||
|
private static LocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<WebhooksManagementResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace LINGYUN.Abp.WebhooksManagement.Authorization; |
||||
|
|
||||
|
public static class WebhooksManagementPermissions |
||||
|
{ |
||||
|
public const string GroupName = "WebhooksManagement"; |
||||
|
|
||||
|
public const string ManageSettings = GroupName + ".ManageSettings"; |
||||
|
} |
||||
@ -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,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 = "WebhooksManagement"; |
||||
|
} |
||||
@ -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,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,10 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhooksManagementApplicationMapperProfile : Profile |
||||
|
{ |
||||
|
public WebhooksManagementApplicationMapperProfile() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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,16 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\dapr\LINGYUN.Abp.Dapr.Client\LINGYUN.Abp.Dapr.Client.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Application.Contracts\LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using LINGYUN.Abp.Dapr.Client; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpDaprClientModule), |
||||
|
typeof(WebhooksManagementApplicationContractsModule))] |
||||
|
public class WebhooksManagementDaprClientModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddDaprClientProxies( |
||||
|
typeof(WebhooksManagementApplicationContractsModule).Assembly, |
||||
|
WebhooksManagementRemoteServiceConsts.RemoteServiceName); |
||||
|
} |
||||
|
} |
||||
@ -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,25 @@ |
|||||
|
<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\WebhooksManagement\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\WebhooksManagement\Localization\Resources\*.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Auditing" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.EventBus" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Localization" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"Features:WebhooksManagement": "WebhooksManagement", |
||||
|
"Permission:WebhooksManagement": "WebhooksManagement", |
||||
|
"Permission:ManageSettings": "Manage Settings" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"Features:WebhooksManagement": "WebhooksManagement", |
||||
|
"Permission:WebhooksManagement": "WebhooksManagement", |
||||
|
"Permission:ManageSettings": "管理设置" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.Localization; |
||||
|
|
||||
|
[LocalizationResourceName("WebhooksManagement")] |
||||
|
public class WebhooksManagementResource |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.ObjectExtending.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.ObjectExtending; |
||||
|
|
||||
|
public class WebhooksManagementModuleExtensionConfiguration : ModuleExtensionConfiguration |
||||
|
{ |
||||
|
public WebhooksManagementModuleExtensionConfiguration ConfigureWebhooksManagement( |
||||
|
Action<EntityExtensionConfiguration> configureAction) |
||||
|
{ |
||||
|
return this.ConfigureEntity( |
||||
|
WebhooksManagementModuleExtensionConsts.EntityNames.Entity, |
||||
|
configureAction |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using Volo.Abp.ObjectExtending.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.ObjectExtending; |
||||
|
|
||||
|
public static class WebhooksManagementModuleExtensionConfigurationDictionaryExtensions |
||||
|
{ |
||||
|
public static ModuleExtensionConfigurationDictionary ConfigureWebhooksManagement( |
||||
|
this ModuleExtensionConfigurationDictionary modules, |
||||
|
Action<WebhooksManagementModuleExtensionConfiguration> configureAction) |
||||
|
{ |
||||
|
return modules.ConfigureModule( |
||||
|
WebhooksManagementModuleExtensionConsts.ModuleName, |
||||
|
configureAction |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace LINGYUN.Abp.WebhooksManagement.ObjectExtending; |
||||
|
|
||||
|
public static class WebhooksManagementModuleExtensionConsts |
||||
|
{ |
||||
|
public const string ModuleName = "WebhooksManagement"; |
||||
|
|
||||
|
public static class EntityNames |
||||
|
{ |
||||
|
public const string Entity = "Entity"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using LINGYUN.Abp.WebhooksManagement.Localization; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Localization.ExceptionHandling; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpLocalizationModule))] |
||||
|
public class WebhooksManagementDomainSharedModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<WebhooksManagementDomainSharedModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<WebhooksManagementResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/WebhooksManagement/Localization/Resources"); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpExceptionLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.MapCodeNamespace(WebhooksManagementErrorCodes.Namespace, typeof(WebhooksManagementResource)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public static class WebhooksManagementErrorCodes |
||||
|
{ |
||||
|
public const string Namespace = "WebhooksManagement"; |
||||
|
} |
||||
@ -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,22 @@ |
|||||
|
<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.AutoMapper" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Caching" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Domain.Shared\LINGYUN.Abp.WebhooksManagement.Domain.Shared.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WebHooks\LINGYUN.Abp.WebHooks.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,70 @@ |
|||||
|
using LINGYUN.Abp.Webhooks; |
||||
|
using System; |
||||
|
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class DefaultWebhookManager : WebhookManager, ITransientDependency |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
||||
|
protected IWebhookSendRecordRepository WebhookSendAttemptRepository { get; } |
||||
|
public DefaultWebhookManager( |
||||
|
ICurrentTenant currentTenant, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IWebhookSendAttemptStore webhookSendAttemptStore, |
||||
|
IUnitOfWorkManager unitOfWorkManager, |
||||
|
IWebhookSendRecordRepository webhookSendAttemptRepository) |
||||
|
: base(jsonSerializer, webhookSendAttemptStore) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
UnitOfWorkManager = unitOfWorkManager; |
||||
|
WebhookSendAttemptRepository = webhookSendAttemptRepository; |
||||
|
} |
||||
|
|
||||
|
public async override Task<Guid> InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs) |
||||
|
{ |
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(webhookSenderArgs.TenantId)) |
||||
|
{ |
||||
|
var record = new WebhookSendRecord( |
||||
|
GuidGenerator.Create(), |
||||
|
webhookSenderArgs.WebhookEventId, |
||||
|
webhookSenderArgs.WebhookSubscriptionId, |
||||
|
webhookSenderArgs.TenantId); |
||||
|
|
||||
|
await WebhookSendAttemptRepository.InsertAsync(record); |
||||
|
|
||||
|
await uow.SaveChangesAsync(); |
||||
|
|
||||
|
return record.Id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async override Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content) |
||||
|
{ |
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
var record = await WebhookSendAttemptRepository.GetAsync(webhookSendAttemptId); |
||||
|
record.SetResponse(content, statusCode); |
||||
|
|
||||
|
await WebhookSendAttemptRepository.UpdateAsync(record); |
||||
|
|
||||
|
await uow.SaveChangesAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public interface IWebhookEventRecordRepository : IRepository<WebhookEventRecord, Guid> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public interface IWebhookSendRecordRepository : IRepository<WebhookSendRecord, Guid> |
||||
|
{ |
||||
|
Task<int> GetCountAsync( |
||||
|
WebhookSendRecordFilter filter, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<WebhookSendRecord>> GetListAsync( |
||||
|
WebhookSendRecordFilter filter, |
||||
|
string sorting = nameof(WebhookSendRecord.CreationTime), |
||||
|
int maxResultCount = 10, |
||||
|
int skipCount = 10, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public interface IWebhookSubscriptionRepository : IRepository<WebhookSubscription, Guid> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.Settings |
||||
|
{ |
||||
|
public class WebhooksManagementSettingDefinitionProvider : SettingDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(ISettingDefinitionContext context) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.WebhooksManagement.Settings |
||||
|
{ |
||||
|
public static class WebhooksManagementSettings |
||||
|
{ |
||||
|
public const string GroupName = "WebhooksManagement"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookEventRecord : Entity<Guid>, IMultiTenant, IHasCreationTime, IHasDeletionTime |
||||
|
{ |
||||
|
public virtual Guid? TenantId { get; protected set; } |
||||
|
public virtual string WebhookName { get; protected set; } |
||||
|
public virtual string Data { get; protected set; } |
||||
|
public virtual DateTime CreationTime { get; set; } |
||||
|
public virtual DateTime? DeletionTime { get; set; } |
||||
|
public virtual bool IsDeleted { get; set; } |
||||
|
protected WebhookEventRecord() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public WebhookEventRecord( |
||||
|
Guid id, |
||||
|
string webhookName, |
||||
|
string data, |
||||
|
Guid? tenantId = null) : base(id) |
||||
|
{ |
||||
|
WebhookName = webhookName; |
||||
|
Data = data; |
||||
|
TenantId = tenantId; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
using LINGYUN.Abp.Webhooks; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookEventStore : DomainService, IWebhookEventStore |
||||
|
{ |
||||
|
protected IObjectMapper<WebhooksManagementDomainModule> ObjectMapper => LazyServiceProvider.LazyGetRequiredService<IObjectMapper<WebhooksManagementDomainModule>>(); |
||||
|
|
||||
|
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
||||
|
protected IWebhookEventRecordRepository WebhookEventRepository { get; } |
||||
|
|
||||
|
public WebhookEventStore( |
||||
|
IUnitOfWorkManager unitOfWorkManager, |
||||
|
IWebhookEventRecordRepository webhookEventRepository) |
||||
|
{ |
||||
|
UnitOfWorkManager = unitOfWorkManager; |
||||
|
WebhookEventRepository = webhookEventRepository; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<WebhookEvent> GetAsync(Guid? tenantId, Guid id) |
||||
|
{ |
||||
|
using var uow = UnitOfWorkManager.Begin(); |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
var record = await WebhookEventRepository.GetAsync(id); |
||||
|
|
||||
|
return ObjectMapper.Map<WebhookEventRecord, WebhookEvent>(record); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<Guid> InsertAndGetIdAsync(WebhookEvent webhookEvent) |
||||
|
{ |
||||
|
using var uow = UnitOfWorkManager.Begin(); |
||||
|
using (CurrentTenant.Change(webhookEvent.TenantId)) |
||||
|
{ |
||||
|
var record = new WebhookEventRecord( |
||||
|
GuidGenerator.Create(), |
||||
|
webhookEvent.WebhookName, |
||||
|
webhookEvent.Data, |
||||
|
webhookEvent.TenantId) |
||||
|
{ |
||||
|
CreationTime = Clock.Now, |
||||
|
}; |
||||
|
|
||||
|
await WebhookEventRepository.InsertAsync(record); |
||||
|
|
||||
|
await uow.SaveChangesAsync(); |
||||
|
|
||||
|
return record.Id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,165 @@ |
|||||
|
using LINGYUN.Abp.Webhooks; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
using Volo.Abp.Linq; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookSendAttemptStore : DomainService, IWebhookSendAttemptStore |
||||
|
{ |
||||
|
protected IObjectMapper<WebhooksManagementDomainModule> ObjectMapper => LazyServiceProvider.LazyGetRequiredService<IObjectMapper<WebhooksManagementDomainModule>>(); |
||||
|
protected IAsyncQueryableExecuter AsyncQueryableExecuter => LazyServiceProvider.LazyGetRequiredService<IAsyncQueryableExecuter>(); |
||||
|
|
||||
|
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
||||
|
protected IWebhookSendRecordRepository WebhookSendAttemptRepository { get; } |
||||
|
|
||||
|
public WebhookSendAttemptStore( |
||||
|
IUnitOfWorkManager unitOfWorkManager, |
||||
|
IWebhookSendRecordRepository webhookSendAttemptRepository) |
||||
|
{ |
||||
|
UnitOfWorkManager = unitOfWorkManager; |
||||
|
WebhookSendAttemptRepository = webhookSendAttemptRepository; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<(int TotalCount, IReadOnlyCollection<WebhookSendAttempt> Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync( |
||||
|
Guid? tenantId, |
||||
|
Guid subscriptionId, |
||||
|
int maxResultCount, |
||||
|
int skipCount) |
||||
|
{ |
||||
|
(int TotalCount, IReadOnlyCollection<WebhookSendAttempt> Webhooks) sendAttempts; |
||||
|
|
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
var filter = new WebhookSendRecordFilter |
||||
|
{ |
||||
|
SubscriptionId = subscriptionId, |
||||
|
}; |
||||
|
var totalCount = await WebhookSendAttemptRepository.GetCountAsync(filter); |
||||
|
|
||||
|
var list = await WebhookSendAttemptRepository.GetListAsync( |
||||
|
filter, |
||||
|
maxResultCount: maxResultCount, |
||||
|
skipCount: skipCount); |
||||
|
|
||||
|
var webHooks = ObjectMapper.Map<List<WebhookSendRecord>, List<WebhookSendAttempt>>(list); |
||||
|
|
||||
|
sendAttempts = ValueTuple.Create(totalCount, webHooks.ToImmutableList()); |
||||
|
} |
||||
|
|
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
|
||||
|
return sendAttempts; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<List<WebhookSendAttempt>> GetAllSendAttemptsByWebhookEventIdAsync( |
||||
|
Guid? tenantId, |
||||
|
Guid webhookEventId) |
||||
|
{ |
||||
|
List<WebhookSendAttempt> sendAttempts; |
||||
|
|
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
var queryable = await WebhookSendAttemptRepository.GetQueryableAsync(); |
||||
|
|
||||
|
var list = await AsyncQueryableExecuter.ToListAsync(queryable |
||||
|
.Where(attempt => attempt.WebhookEventId == webhookEventId) |
||||
|
.OrderByDescending(attempt => attempt.CreationTime) |
||||
|
); |
||||
|
|
||||
|
sendAttempts = ObjectMapper.Map<List<WebhookSendRecord>, List<WebhookSendAttempt>>(list); |
||||
|
} |
||||
|
|
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
|
||||
|
return sendAttempts; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<WebhookSendAttempt> GetAsync( |
||||
|
Guid? tenantId, |
||||
|
Guid id) |
||||
|
{ |
||||
|
WebhookSendRecord sendAttempt; |
||||
|
|
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
sendAttempt = await WebhookSendAttemptRepository.GetAsync(id); |
||||
|
} |
||||
|
|
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
|
||||
|
return ObjectMapper.Map<WebhookSendRecord, WebhookSendAttempt>(sendAttempt); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<int> GetSendAttemptCountAsync( |
||||
|
Guid? tenantId, |
||||
|
Guid webhookEventId, |
||||
|
Guid webhookSubscriptionId) |
||||
|
{ |
||||
|
int sendAttemptCount; |
||||
|
|
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
sendAttemptCount = await WebhookSendAttemptRepository.CountAsync(attempt => |
||||
|
attempt.WebhookEventId == webhookEventId && |
||||
|
attempt.WebhookSubscriptionId == webhookSubscriptionId); |
||||
|
} |
||||
|
|
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
|
||||
|
return sendAttemptCount; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<bool> HasXConsecutiveFailAsync( |
||||
|
Guid? tenantId, |
||||
|
Guid subscriptionId, |
||||
|
int failCount) |
||||
|
{ |
||||
|
bool result; |
||||
|
|
||||
|
using (var uow = UnitOfWorkManager.Begin()) |
||||
|
{ |
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
if (await WebhookSendAttemptRepository.CountAsync(x => x.WebhookSubscriptionId == subscriptionId) < failCount) |
||||
|
{ |
||||
|
result = false; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var queryable = await WebhookSendAttemptRepository.GetQueryableAsync(); |
||||
|
|
||||
|
result = !await AsyncQueryableExecuter.AnyAsync(queryable |
||||
|
.OrderByDescending(attempt => attempt.CreationTime) |
||||
|
.Take(failCount) |
||||
|
.Where(attempt => attempt.ResponseStatusCode == HttpStatusCode.OK) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using System; |
||||
|
using System.Net; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookSendRecord : Entity<Guid>, IHasCreationTime, IHasModificationTime, IMultiTenant |
||||
|
{ |
||||
|
public virtual Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
public virtual Guid WebhookEventId { get; protected set; } |
||||
|
|
||||
|
public virtual Guid WebhookSubscriptionId { get; protected set; } |
||||
|
|
||||
|
public virtual string Response { get; protected set; } |
||||
|
|
||||
|
public virtual HttpStatusCode? ResponseStatusCode { get; set; } |
||||
|
|
||||
|
public virtual DateTime CreationTime { get; set; } |
||||
|
|
||||
|
public virtual DateTime? LastModificationTime { get; set; } |
||||
|
|
||||
|
public virtual WebhookEventRecord WebhookEvent { get; protected set; } |
||||
|
|
||||
|
protected WebhookSendRecord() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public WebhookSendRecord( |
||||
|
Guid id, |
||||
|
Guid eventId, |
||||
|
Guid subscriptionId, |
||||
|
Guid? tenantId = null) : base(id) |
||||
|
{ |
||||
|
WebhookEventId = eventId; |
||||
|
WebhookSubscriptionId = subscriptionId; |
||||
|
|
||||
|
TenantId = tenantId; |
||||
|
} |
||||
|
|
||||
|
public void SetResponse( |
||||
|
string response, |
||||
|
HttpStatusCode? statusCode = null) |
||||
|
{ |
||||
|
Response = response; |
||||
|
ResponseStatusCode = statusCode; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using System.Net; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookSendRecordFilter |
||||
|
{ |
||||
|
public string Filter { get; set; } |
||||
|
|
||||
|
public Guid? WebhookEventId { get; set; } |
||||
|
|
||||
|
public Guid? SubscriptionId { get; set; } |
||||
|
|
||||
|
public string Response { get; set; } |
||||
|
|
||||
|
public HttpStatusCode? ResponseStatusCode { get; set; } |
||||
|
|
||||
|
public DateTime? BeginCreationTime { get; set; } |
||||
|
|
||||
|
public DateTime? EndCreationTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Domain.Entities.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookSubscription : CreationAuditedEntity<Guid> |
||||
|
{ |
||||
|
public virtual Guid? TenantId { get; protected set; } |
||||
|
public virtual string WebhookUri { get; protected set; } |
||||
|
public virtual string Secret { get; protected set; } |
||||
|
public virtual bool IsActive { get; set; } |
||||
|
public virtual string Webhooks { get; protected set; } |
||||
|
public virtual string Headers { get; protected set; } |
||||
|
protected WebhookSubscription() |
||||
|
{ |
||||
|
} |
||||
|
public WebhookSubscription( |
||||
|
Guid id, |
||||
|
string webhookUri, |
||||
|
string secret, |
||||
|
string webhooks, |
||||
|
string headers, |
||||
|
Guid? tenantId = null) : base(id) |
||||
|
{ |
||||
|
WebhookUri = webhookUri; |
||||
|
Secret = secret; |
||||
|
Webhooks = webhooks; |
||||
|
Headers = headers; |
||||
|
TenantId = tenantId; |
||||
|
|
||||
|
IsActive = true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using LINGYUN.Abp.Webhooks; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhookSubscriptionsStore : DomainService, IWebhookSubscriptionsStore |
||||
|
{ |
||||
|
public Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<WebhookSubscriptionInfo>> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task<WebhookSubscriptionInfo> GetAsync(Guid id) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task InsertAsync(WebhookSubscriptionInfo webhookSubscription) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task<bool> IsSubscribedAsync(Guid? tenantId, string webhookName) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public static class WebhooksManagementDbProperties |
||||
|
{ |
||||
|
public static string DbTablePrefix { get; set; } = "WebhooksManagement_"; |
||||
|
|
||||
|
public static string DbSchema { get; set; } = null; |
||||
|
|
||||
|
|
||||
|
public const string ConnectionStringName = "WebhooksManagement"; |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
public class WebhooksManagementDomainMapperProfile : Profile |
||||
|
{ |
||||
|
public WebhooksManagementDomainMapperProfile() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using LINGYUN.Abp.Webhooks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Domain.Entities.Events.Distributed; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpAutoMapperModule), |
||||
|
typeof(AbpWebhooksModule), |
||||
|
typeof(WebhooksManagementDomainSharedModule))] |
||||
|
public class WebhooksManagementDomainModule : AbpModule |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new(); |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<WebhooksManagementDomainModule>(); |
||||
|
|
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<WebhooksManagementDomainMapperProfile>(validate: true); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpDistributedEntityEventOptions>(options => |
||||
|
{ |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void PostConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
// 扩展实体配置
|
||||
|
//ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity(
|
||||
|
// WebhooksManagementModuleExtensionConsts.ModuleName,
|
||||
|
// WebhooksManagementModuleExtensionConsts.EntityNames.Entity,
|
||||
|
// typeof(Entity)
|
||||
|
//);
|
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Domain\LINGYUN.Abp.WebhooksManagement.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; |
||||
|
|
||||
|
[ConnectionStringName(WebhooksManagementDbProperties.ConnectionStringName)] |
||||
|
public interface IWebhooksManagementDbContext : IEfCoreDbContext |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; |
||||
|
|
||||
|
[ConnectionStringName(WebhooksManagementDbProperties.ConnectionStringName)] |
||||
|
public class WebhooksManagementDbContext : AbpDbContext<WebhooksManagementDbContext>, IWebhooksManagementDbContext |
||||
|
{ |
||||
|
public WebhooksManagementDbContext( |
||||
|
DbContextOptions<WebhooksManagementDbContext> options) : base(options) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder) |
||||
|
{ |
||||
|
base.OnModelCreating(modelBuilder); |
||||
|
|
||||
|
modelBuilder.ConfigureWebhooksManagement(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; |
||||
|
|
||||
|
public static class WebhooksManagementDbContextModelCreatingExtensions |
||||
|
{ |
||||
|
public static void ConfigureWebhooksManagement( |
||||
|
this ModelBuilder builder, |
||||
|
Action<WebhooksManagementModelBuilderConfigurationOptions> optionsAction = null) |
||||
|
{ |
||||
|
Check.NotNull(builder, nameof(builder)); |
||||
|
|
||||
|
var options = new WebhooksManagementModelBuilderConfigurationOptions( |
||||
|
WebhooksManagementDbProperties.DbTablePrefix, |
||||
|
WebhooksManagementDbProperties.DbSchema |
||||
|
); |
||||
|
optionsAction?.Invoke(options); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; |
||||
|
|
||||
|
public static class WebhooksManagementEfCoreQueryableExtensions |
||||
|
{ |
||||
|
// 在此聚合仓储服务的扩展方法
|
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(WebhooksManagementDomainModule), |
||||
|
typeof(AbpEntityFrameworkCoreModule))] |
||||
|
public class WebhooksManagementEntityFrameworkCoreModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAbpDbContext<WebhooksManagementDbContext>(options => |
||||
|
{ |
||||
|
options.AddDefaultRepositories<IWebhooksManagementDbContext>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.EntityFrameworkCore.Modeling; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; |
||||
|
|
||||
|
public class WebhooksManagementModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions |
||||
|
{ |
||||
|
public WebhooksManagementModelBuilderConfigurationOptions( |
||||
|
[NotNull] string tablePrefix = "", |
||||
|
[CanBeNull] string schema = null) |
||||
|
: base( |
||||
|
tablePrefix, |
||||
|
schema) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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,19 @@ |
|||||
|
<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.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Application.Contracts\LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Http.Client; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.WebhooksManagement; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpHttpClientModule), |
||||
|
typeof(WebhooksManagementApplicationContractsModule))] |
||||
|
public class WebhooksManagementHttpApiClientModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddHttpClientProxies( |
||||
|
typeof(WebhooksManagementApplicationContractsModule).Assembly, |
||||
|
WebhooksManagementRemoteServiceConsts.RemoteServiceName); |
||||
|
} |
||||
|
} |
||||
@ -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,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.WebhooksManagement.Application.Contracts\LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue