diff --git a/aspnet-core/Directory.Build.props b/aspnet-core/Directory.Build.props index 969253fd5..9f9403ebe 100644 --- a/aspnet-core/Directory.Build.props +++ b/aspnet-core/Directory.Build.props @@ -12,6 +12,11 @@ 2.0.3 1.7.28 7.15.1 + 1.0.0-rc8 + 1.2.0-rc1 + 1.0.0-rc8 + 1.0.0-rc8 + 1.0.0-beta2 3.3.3 2.0.593 2.10.0 diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 2d20d15c8..3bda2e147 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -394,6 +394,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Saas.HttpApi.Cl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MultiTenancy.Saas", "modules\tenants\LINGYUN.Abp.MultiTenancy.Saas\LINGYUN.Abp.MultiTenancy.Saas.csproj", "{F57594AA-10C2-4DFF-87F6-19F2548099EA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "webhooks", "webhooks", "{13ACF670-F109-404E-B252-2FA34A4EA061}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WebHooks", "modules\webhooks\LINGYUN.Abp.WebHooks\LINGYUN.Abp.WebHooks.csproj", "{91AE01B1-CC82-40E2-8290-B8A84C6E90D1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1020,6 +1024,10 @@ Global {F57594AA-10C2-4DFF-87F6-19F2548099EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {F57594AA-10C2-4DFF-87F6-19F2548099EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {F57594AA-10C2-4DFF-87F6-19F2548099EA}.Release|Any CPU.Build.0 = Release|Any CPU + {91AE01B1-CC82-40E2-8290-B8A84C6E90D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91AE01B1-CC82-40E2-8290-B8A84C6E90D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91AE01B1-CC82-40E2-8290-B8A84C6E90D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91AE01B1-CC82-40E2-8290-B8A84C6E90D1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1212,6 +1220,8 @@ Global {8DF50094-6791-4C7C-B07D-C3E995B69C49} = {D01D859E-4B72-478A-BABD-90F0981652D5} {96EF6CDD-CD29-4E7B-B86A-3EBEE6AC9FDC} = {D01D859E-4B72-478A-BABD-90F0981652D5} {F57594AA-10C2-4DFF-87F6-19F2548099EA} = {A5543E56-DA53-494D-A531-DA75091D46FF} + {13ACF670-F109-404E-B252-2FA34A4EA061} = {C5CAD011-DF84-4914-939C-0C029DCEF26F} + {91AE01B1-CC82-40E2-8290-B8A84C6E90D1} = {13ACF670-F109-404E-B252-2FA34A4EA061} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index e2fdcc2f0..edeb3b22c 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -226,6 +226,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Actors.Asp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MultiTenancy.Editions", "modules\tenants\LINGYUN.Abp.MultiTenancy.Editions\LINGYUN.Abp.MultiTenancy.Editions.csproj", "{3FF4CEA0-1555-4D62-AA81-B3B599253F8D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "webhooks", "webhooks", "{BD97C98B-0B4B-443D-AB29-145A344F46D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WebHooks", "modules\webhooks\LINGYUN.Abp.WebHooks\LINGYUN.Abp.WebHooks.csproj", "{AFE75D2B-8853-488B-B5D5-277B58C5DBB2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -580,6 +584,10 @@ Global {3FF4CEA0-1555-4D62-AA81-B3B599253F8D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3FF4CEA0-1555-4D62-AA81-B3B599253F8D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3FF4CEA0-1555-4D62-AA81-B3B599253F8D}.Release|Any CPU.Build.0 = Release|Any CPU + {AFE75D2B-8853-488B-B5D5-277B58C5DBB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFE75D2B-8853-488B-B5D5-277B58C5DBB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFE75D2B-8853-488B-B5D5-277B58C5DBB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFE75D2B-8853-488B-B5D5-277B58C5DBB2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -692,6 +700,8 @@ Global {FF518E10-C9AB-440C-8E8D-9CFF67A926AC} = {3A0784A6-AFBF-406F-B79E-9505EB100445} {49E0B90B-8635-43D0-B0AB-9D484CAE68B5} = {7FDFB22F-1BFF-4E05-9427-78B7A8461D50} {3FF4CEA0-1555-4D62-AA81-B3B599253F8D} = {38E21687-5F19-42C9-9D11-4B1D2EF64EDB} + {BD97C98B-0B4B-443D-AB29-145A344F46D3} = {02EA4E78-5891-43BC-944F-3E52FEE032E4} + {AFE75D2B-8853-488B-B5D5-277B58C5DBB2} = {BD97C98B-0B4B-443D-AB29-145A344F46D3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj new file mode 100644 index 000000000..251dd53d3 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN.Abp.WebHooks.ClientProxies.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.0 + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs new file mode 100644 index 000000000..41e08a4a7 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/AbpWebHooksClientProxiesModule.cs @@ -0,0 +1,10 @@ +using LINGYUN.Abp.WebhooksManagement; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Webhooks.ClientProxies; + +[DependsOn(typeof(AbpWebhooksModule))] +[DependsOn(typeof(WebhooksManagementHttpApiClientModule))] +public class AbpWebHooksClientProxiesModule : AbpModule +{ +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs new file mode 100644 index 000000000..9e5ae8b35 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks.ClientProxies/LINGYUN/Abp/WebHooks/ClientProxies/ClientProxiesWebhookPublisher.cs @@ -0,0 +1,91 @@ +using LINGYUN.Abp.WebhooksManagement; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Webhooks.ClientProxies; + +[Dependency(ReplaceServices = true)] +public class ClientProxiesWebhookPublisher : IWebhookPublisher, ITransientDependency +{ + protected IWebhookPublishAppService PublishAppService { get; } + + public ClientProxiesWebhookPublisher( + IWebhookPublishAppService publishAppService) + { + PublishAppService = publishAppService; + } + + public async virtual Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null) + { + var input = new WebhookPublishInput + { + WebhookName = webhookName, + Data = JsonConvert.SerializeObject(data), + SendExactSameData = sendExactSameData, + }; + if (headers != null) + { + input.Header = new WebhooksHeaderInput + { + UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, + Headers = headers.Headers + }; + } + + await PublishAsync(input); + } + + public async virtual Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null) + { + var input = new WebhookPublishInput + { + WebhookName = webhookName, + Data = JsonConvert.SerializeObject(data), + SendExactSameData = sendExactSameData, + TenantIds = new List + { + tenantId + }, + }; + if (headers != null) + { + input.Header = new WebhooksHeaderInput + { + UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, + Headers = headers.Headers + }; + } + + await PublishAsync(input); + } + + public async virtual Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null) + { + var input = new WebhookPublishInput + { + WebhookName = webhookName, + Data = JsonConvert.SerializeObject(data), + SendExactSameData = sendExactSameData, + TenantIds = tenantIds.ToList(), + }; + if (headers != null) + { + input.Header = new WebhooksHeaderInput + { + UseOnlyGivenHeaders = headers.UseOnlyGivenHeaders, + Headers = headers.Headers + }; + } + + await PublishAsync(input); + } + + protected virtual async Task PublishAsync(WebhookPublishInput input) + { + await PublishAppService.PublishAsync(input); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml new file mode 100644 index 000000000..17d32672d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj new file mode 100644 index 000000000..2cd64673f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN.Abp.WebHooks.csproj @@ -0,0 +1,18 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs new file mode 100644 index 000000000..84798d8d2 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksModule.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.Features; +using Volo.Abp.Guids; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Webhooks; + +//[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))] +// 防止未引用实现无法发布到后台作业 +[DependsOn(typeof(AbpBackgroundJobsModule))] +[DependsOn(typeof(AbpFeaturesModule))] +[DependsOn(typeof(AbpGuidsModule))] +[DependsOn(typeof(AbpHttpClientModule))] +public class AbpWebhooksModule : AbpModule +{ + internal const string WebhooksClient = "__Abp_Webhooks_HttpClient"; + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var options = context.Services.ExecutePreConfiguredActions(); + + context.Services.AddHttpClient(WebhooksClient, client => + { + client.Timeout = options.TimeoutDuration; + }); + } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => + { + if (typeof(WebhookDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs new file mode 100644 index 000000000..072a76bcc --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs @@ -0,0 +1,35 @@ +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 DefinitionProviders { get; } + + public AbpWebhooksOptions() + { + TimeoutDuration = TimeSpan.FromSeconds(60); + MaxSendAttemptCount = 5; + MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; + + DefinitionProviders = new TypeList(); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs new file mode 100644 index 000000000..b411e3a84 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/BackgroundWorker/WebhookSenderJob.cs @@ -0,0 +1,131 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.Webhooks.BackgroundWorker +{ + public class WebhookSenderJob : AsyncBackgroundJob, ITransientDependency + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IWebhookDefinitionManager _webhookDefinitionManager; + private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; + private readonly IWebhookSendAttemptStore _webhookSendAttemptStore; + private readonly IWebhookSender _webhookSender; + + private readonly AbpWebhooksOptions _options; + + public WebhookSenderJob( + IUnitOfWorkManager unitOfWorkManager, + IWebhookDefinitionManager webhookDefinitionManager, + IWebhookSubscriptionManager webhookSubscriptionManager, + IWebhookSendAttemptStore webhookSendAttemptStore, + IWebhookSender webhookSender, + IOptions options) + { + _unitOfWorkManager = unitOfWorkManager; + _webhookDefinitionManager = webhookDefinitionManager; + _webhookSubscriptionManager = webhookSubscriptionManager; + _webhookSendAttemptStore = webhookSendAttemptStore; + _webhookSender = webhookSender; + _options = options.Value; + } + + public override async Task ExecuteAsync(WebhookSenderArgs args) + { + var webhookDefinition = _webhookDefinitionManager.Get(args.WebhookName); + + if (webhookDefinition.TryOnce) + { + try + { + await SendWebhook(args, webhookDefinition); + } + catch (Exception e) + { + Logger.LogWarning("An error occured while sending webhook with try once.", e); + // ignored + } + } + else + { + await SendWebhook(args, webhookDefinition); + } + } + + private async Task SendWebhook(WebhookSenderArgs args, WebhookDefinition webhookDefinition) + { + if (args.WebhookEventId == default) + { + return; + } + + if (args.WebhookSubscriptionId == default) + { + return; + } + + if (!webhookDefinition.TryOnce) + { + var sendAttemptCount = await _webhookSendAttemptStore.GetSendAttemptCountAsync( + args.TenantId, + args.WebhookEventId, + args.WebhookSubscriptionId + ); + + if ((webhookDefinition.MaxSendAttemptCount > 0 && sendAttemptCount > webhookDefinition.MaxSendAttemptCount) || + sendAttemptCount > _options.MaxSendAttemptCount) + { + return; + } + } + + try + { + await _webhookSender.SendWebhookAsync(args); + } + catch (Exception) + { + // no need to retry to send webhook since subscription disabled + if (!await TryDeactivateSubscriptionIfReachedMaxConsecutiveFailCount( + args.TenantId, + args.WebhookSubscriptionId)) + { + throw; //Throw exception to re-try sending webhook + } + } + } + + private async Task 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; + } + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs new file mode 100644 index 000000000..be46197ae --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookPublisher.cs @@ -0,0 +1,140 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Webhooks +{ + public class DefaultWebhookPublisher : IWebhookPublisher, ITransientDependency + { + public IWebhookEventStore WebhookEventStore { get; set; } + + private readonly ICurrentTenant _currentTenant; + private readonly IBackgroundJobManager _backgroundJobManager; + private readonly IWebhookSubscriptionManager _webhookSubscriptionManager; + + public DefaultWebhookPublisher( + IWebhookSubscriptionManager webhookSubscriptionManager, + ICurrentTenant currentTenant, + IBackgroundJobManager backgroundJobManager) + { + _currentTenant = currentTenant; + _backgroundJobManager = backgroundJobManager; + _webhookSubscriptionManager = webhookSubscriptionManager; + + WebhookEventStore = NullWebhookEventStore.Instance; + } + + #region Async Publish Methods + + public virtual async Task PublishAsync( + string webhookName, + object data, + bool sendExactSameData = false, + WebhookHeader headers = null) + { + var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(_currentTenant.Id, webhookName); + await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); + } + + public virtual async Task PublishAsync( + string webhookName, + object data, + Guid? tenantId, + bool sendExactSameData = false, + WebhookHeader headers = null) + { + var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsIfFeaturesGrantedAsync(tenantId, webhookName); + await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); + } + + public virtual async Task PublishAsync( + Guid?[] tenantIds, + string webhookName, + object data, + bool sendExactSameData = false, + WebhookHeader headers = null) + { + var subscriptions = await _webhookSubscriptionManager.GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(tenantIds, webhookName); + await PublishAsync(webhookName, data, subscriptions, sendExactSameData, headers); + } + + protected virtual async Task PublishAsync( + string webhookName, + object data, + List 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 SaveAndGetWebhookAsync( + Guid? tenantId, + string webhookName, + object data) + { + var webhookInfo = new WebhookEvent + { + WebhookName = webhookName, + Data = JsonConvert.SerializeObject(data), + TenantId = tenantId + }; + + var webhookId = await WebhookEventStore.InsertAndGetIdAsync(webhookInfo); + webhookInfo.Id = webhookId; + + return webhookInfo; + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs new file mode 100644 index 000000000..19982a68e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/DefaultWebhookSender.cs @@ -0,0 +1,129 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Webhooks +{ + public class DefaultWebhookSender : IWebhookSender, ITransientDependency + { + public ILogger Logger { protected get; set; } + + private readonly IWebhookManager _webhookManager; + private readonly IHttpClientFactory _httpClientFactory; + + private const string FailedRequestDefaultContent = "Webhook Send Request Failed"; + + public DefaultWebhookSender( + IWebhookManager webhookManager, + IHttpClientFactory httpClientFactory) + { + _webhookManager = webhookManager; + _httpClientFactory = httpClientFactory; + + Logger = NullLogger.Instance; + } + + public async Task 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; + } + + /// + /// You can override this to change request message + /// + /// + protected virtual HttpRequestMessage CreateWebhookRequestMessage(WebhookSenderArgs webhookSenderArgs) + { + return new HttpRequestMessage(HttpMethod.Post, webhookSenderArgs.WebhookUri); + } + + protected virtual void AddAdditionalHeaders(HttpRequestMessage request, WebhookSenderArgs webhookSenderArgs) + { + foreach (var header in webhookSenderArgs.Headers) + { + if (request.Headers.TryAddWithoutValidation(header.Key, header.Value)) + { + continue; + } + + if (request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value)) + { + continue; + } + + throw new Exception($"Invalid Header. SubscriptionId:{webhookSenderArgs.WebhookSubscriptionId},Header: {header.Key}:{header.Value}"); + } + } + + protected virtual async Task<(bool isSucceed, HttpStatusCode statusCode, string content)> SendHttpRequest(HttpRequestMessage request) + { + var client = _httpClientFactory.CreateClient(AbpWebhooksModule.WebhooksClient); + + var response = await client.SendAsync(request); + + var isSucceed = response.IsSuccessStatusCode; + var statusCode = response.StatusCode; + var content = await response.Content.ReadAsStringAsync(); + + return (isSucceed, statusCode, content); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs new file mode 100644 index 000000000..8cf48f520 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/Extensions/WebhookSubscriptionExtensions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks.Extensions +{ + public static class WebhookSubscriptionExtensions + { + /// + /// checks if subscribed to given webhook + /// + /// + public static bool IsSubscribed(this WebhookSubscriptionInfo webhookSubscription, string webhookName) + { + if (webhookSubscription.Webhooks.IsNullOrEmpty()) + { + return false; + } + + return webhookSubscription.Webhooks.Contains(webhookName); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs new file mode 100644 index 000000000..c170ede3d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionContext.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookDefinitionContext + { + WebhookGroupDefinition AddGroup( + [NotNull] string name, + ILocalizableString displayName = null); + + WebhookGroupDefinition GetGroupOrNull(string name); + + void RemoveGroup(string name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs new file mode 100644 index 000000000..a284ab693 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookDefinitionManager.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookDefinitionManager + { + /// + /// Gets a webhook definition by name. + /// Returns null if there is no webhook definition with given name. + /// + WebhookDefinition GetOrNull(string name); + + /// + /// Gets a webhook definition by name. + /// Throws exception if there is no webhook definition with given name. + /// + WebhookDefinition Get(string name); + + /// + /// Gets all webhook definitions. + /// + IReadOnlyList GetAll(); + + /// + /// Gets all webhook group definitions. + /// + /// + IReadOnlyList GetGroups(); + + /// + /// Checks if given webhook name is available for given tenant. + /// + Task IsAvailableAsync(Guid? tenantId, string name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs new file mode 100644 index 000000000..1fb8f5c21 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookEventStore.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookEventStore + { + /// + /// Inserts to persistent store + /// + Task InsertAndGetIdAsync(WebhookEvent webhookEvent); + + /// + /// Gets Webhook info by id + /// + Task GetAsync(Guid? tenantId, Guid id); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs new file mode 100644 index 000000000..1e8e5fd0c --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookManager.cs @@ -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 GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs); + + void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret); + + Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs); + + Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); + + Task StoreResponseOnWebhookSendAttemptAsync( + Guid webhookSendAttemptId, Guid? tenantId, + HttpStatusCode? statusCode, string content); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs new file mode 100644 index 000000000..3b53fec83 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookPublisher.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookPublisher + { + /// + /// Sends webhooks to current tenant subscriptions (). with given data, (Checks permissions) + /// + /// + /// data to send + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + /// 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. + Task PublishAsync(string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); + + /// + /// Sends webhooks to given tenant's subscriptions + /// + /// + /// data to send + /// + /// Target tenant id + /// + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + /// 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. + Task PublishAsync(string webhookName, object data, Guid? tenantId, bool sendExactSameData = false, WebhookHeader headers = null); + + /// + /// Sends webhooks to given tenant's subscriptions + /// + /// + /// data to send + /// + /// Target tenant id(s) + /// + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + /// 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. + Task PublishAsync(Guid?[] tenantIds, string webhookName, object data, bool sendExactSameData = false, WebhookHeader headers = null); + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs new file mode 100644 index 000000000..2e32bc63f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSendAttemptStore.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookSendAttemptStore + { + Task GetAsync(Guid? tenantId, Guid id); + + /// + /// Returns work item count by given web hook id and subscription id, (How many times publisher tried to send web hook) + /// + Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId); + + /// + /// Checks is there any successful webhook attempt in last items. Should return true if there are not X number items + /// + Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount); + + Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, int skipCount); + + Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs new file mode 100644 index 000000000..d30e110e6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSender.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookSender + { + /// + /// Tries to send webhook with given transactionId and stores process in + /// Should throw exception if fails or response status not succeed + /// + /// arguments + /// Webhook send attempt id + Task SendWebhookAsync(WebhookSenderArgs webhookSenderArgs); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs new file mode 100644 index 000000000..956b2dbd3 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public interface IWebhookSubscriptionManager + { + /// + /// Returns subscription for given id. + /// + /// Unique identifier of + Task GetAsync(Guid id); + + /// + /// Returns all subscriptions of tenant + /// + /// + /// Target tenant id. + /// + Task> GetAllSubscriptionsAsync(Guid? tenantId); + + /// + /// Returns all subscriptions for given webhook. + /// + /// + /// + /// Target tenant id. + /// + Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName); + + /// + /// Returns all subscriptions of tenant + /// + /// + Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); + + /// + /// Returns all subscriptions for given webhook. + /// + /// + /// + /// Target tenant id(s). + /// + Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName); + + /// + /// Checks if tenant subscribed for a webhook. (Checks if webhook features are granted) + /// + /// + /// Target tenant id(s). + /// + /// + Task IsSubscribedAsync(Guid? tenantId, string webhookName); + + /// + /// If id is the default(Guid) adds new subscription, else updates current one. (Checks if webhook features are granted) + /// + Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription); + + /// + /// Activates/Deactivates given webhook subscription + /// + /// unique identifier of + /// IsActive + Task ActivateWebhookSubscriptionAsync(Guid id, bool active); + + /// + /// Delete given webhook subscription. + /// + /// unique identifier of + Task DeleteSubscriptionAsync(Guid id); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs new file mode 100644 index 000000000..23849ad4e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/IWebhookSubscriptionsStore.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// This interface should be implemented by vendors to make webhooks working. + /// + public interface IWebhookSubscriptionsStore + { + /// + /// returns subscription + /// + /// webhook subscription id + /// + Task GetAsync(Guid id); + + /// + /// Saves webhook subscription to a persistent store. + /// + /// webhook subscription information + Task InsertAsync(WebhookSubscriptionInfo webhookSubscription); + + /// + /// Updates webhook subscription to a persistent store. + /// + /// webhook subscription information + Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription); + + /// + /// Deletes subscription if exists + /// + /// primary key + /// + Task DeleteAsync(Guid id); + + /// + /// Returns all subscriptions of given tenant including deactivated + /// + /// + /// Target tenant id. + /// + Task> GetAllSubscriptionsAsync(Guid? tenantId); + + /// + /// Returns webhook subscriptions which subscribe to given webhook on tenant(s) + /// + /// + /// Target tenant id. + /// + /// + /// + Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName); + + /// + /// Returns all subscriptions of given tenant including deactivated + /// + /// + /// Target tenant id(s). + /// + Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds); + + /// + /// Returns webhook subscriptions which subscribe to given webhook on tenant(s) + /// + /// + /// Target tenant id(s). + /// + /// + /// + Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName); + + /// + /// Checks if tenant subscribed for a webhook + /// + /// + /// Target tenant id(s). + /// + /// Name of the webhook + Task IsSubscribedAsync(Guid? tenantId, string webhookName); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs new file mode 100644 index 000000000..069726e67 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookEventStore.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Null pattern implementation of . + /// It's used if is not implemented by actual persistent store + /// + public class NullWebhookEventStore : IWebhookEventStore + { + public static NullWebhookEventStore Instance { get; } = new NullWebhookEventStore(); + + public Task InsertAndGetIdAsync(WebhookEvent webhookEvent) + { + return Task.FromResult(default); + } + + public Task GetAsync(Guid? tenantId, Guid id) + { + return Task.FromResult(default); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs new file mode 100644 index 000000000..7254b1ae6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSendAttemptStore.cs @@ -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 GetAsync(Guid? tenantId, Guid id) + { + return Task.FromResult(default); + } + + public Task GetSendAttemptCountAsync(Guid? tenantId, Guid webhookId, Guid webhookSubscriptionId) + { + return Task.FromResult(int.MaxValue); + } + + public Task HasXConsecutiveFailAsync(Guid? tenantId, Guid subscriptionId, int searchCount) + { + return default; + } + + public Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync(Guid? tenantId, Guid subscriptionId, int maxResultCount, + int skipCount) + { + return Task.FromResult(ValueTuple.Create(0, new List() as IReadOnlyCollection)); + } + + public Task> GetAllSendAttemptsByWebhookEventIdAsync(Guid? tenantId, Guid webhookEventId) + { + return Task.FromResult(new List()); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs new file mode 100644 index 000000000..4214d1d25 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/NullWebhookSubscriptionsStore.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Null pattern implementation of . + /// It's used if is not implemented by actual persistent store + /// + public class NullWebhookSubscriptionsStore : IWebhookSubscriptionsStore + { + public static NullWebhookSubscriptionsStore Instance { get; } = new NullWebhookSubscriptionsStore(); + + public Task GetAsync(Guid id) + { + return Task.FromResult(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> GetAllSubscriptionsAsync(Guid? tenantId) + { + return Task.FromResult(new List()); + } + + public Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) + { + return Task.FromResult(new List()); + } + + public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) + { + return Task.FromResult(new List()); + } + + public Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) + { + return Task.FromResult(new List()); + } + + public Task IsSubscribedAsync(Guid? tenantId, string webhookName) + { + return Task.FromResult(false); + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs new file mode 100644 index 000000000..1c1461e8a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinition.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookDefinition + { + /// + /// Unique name of the webhook. + /// + public string Name { get; } + + /// + /// Tries to send webhook only one time without checking to send attempt count + /// + public bool TryOnce { get; set; } + + /// + /// Defined maximum number of sending times + /// + public int MaxSendAttemptCount { get; set; } + + /// + /// Display name of the webhook. + /// Optional. + /// + public ILocalizableString DisplayName { get; set; } + + /// + /// Description for the webhook. + /// Optional. + /// + public ILocalizableString Description { get; set; } + + public List 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(); + } + + public WebhookDefinition WithFeature(params string[] features) + { + if (!features.IsNullOrEmpty()) + { + RequiredFeatures.AddRange(features); + } + + return this; + } + + public override string ToString() + { + return $"[{nameof(WebhookDefinition)} {Name}]"; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs new file mode 100644 index 000000000..5e8987a8b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionContext.cs @@ -0,0 +1,55 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookDefinitionContext : IWebhookDefinitionContext + { + protected Dictionary Groups { get; } + + public WebhookDefinitionContext(Dictionary webhooks) + { + Groups = webhooks; + } + + public WebhookGroupDefinition AddGroup( + [NotNull] string name, + ILocalizableString displayName = null) + { + Check.NotNull(name, nameof(name)); + + if (Groups.ContainsKey(name)) + { + throw new AbpException($"There is already an existing webhook group with name: {name}"); + } + + return Groups[name] = new WebhookGroupDefinition(name, displayName); + } + + public WebhookGroupDefinition GetGroupOrNull([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + return null; + } + + return Groups[name]; + } + + public void RemoveGroup(string name) + { + Check.NotNull(name, nameof(name)); + + if (!Groups.ContainsKey(name)) + { + throw new AbpException($"Undefined notification webhook group: '{name}'."); + } + + Groups.Remove(name); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs new file mode 100644 index 000000000..c0f96f20e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs @@ -0,0 +1,139 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.Webhooks +{ + internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency + { + protected IDictionary WebhookGroupDefinitions => _lazyWebhookGroupDefinitions.Value; + private readonly Lazy> _lazyWebhookGroupDefinitions; + + protected IDictionary WebhookDefinitions => _lazyWebhookDefinitions.Value; + private readonly Lazy> _lazyWebhookDefinitions; + + private readonly IServiceProvider _serviceProvider; + private readonly AbpWebhooksOptions _options; + + public WebhookDefinitionManager( + IServiceProvider serviceProvider, + IOptions options) + { + _serviceProvider = serviceProvider; + _options = options.Value; + + _lazyWebhookGroupDefinitions = new Lazy>(CreateWebhookGroupDefinitions); + _lazyWebhookDefinitions = new Lazy>(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 GetAll() + { + return WebhookDefinitions.Values.ToImmutableList(); + } + + public IReadOnlyList GetGroups() + { + return WebhookGroupDefinitions.Values.ToImmutableList(); + } + + public async Task 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(); + var featureChecker = _serviceProvider.GetRequiredService(); + using (currentTenant.Change(tenantId)) + { + if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray())) + { + return false; + } + } + + return true; + } + + protected virtual Dictionary CreateWebhookDefinitions() + { + var definitions = new Dictionary(); + + foreach (var groupDefinition in WebhookGroupDefinitions.Values) + { + foreach (var webhook in groupDefinition.Webhooks) + { + if (definitions.ContainsKey(webhook.Name)) + { + throw new AbpException("Duplicate webhook name: " + webhook.Name); + } + + definitions[webhook.Name] = webhook; + } + } + + return definitions; + } + + protected virtual Dictionary CreateWebhookGroupDefinitions() + { + var definitions = new Dictionary(); + + 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; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs new file mode 100644 index 000000000..ead9ab679 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookDefinitionProvider.cs @@ -0,0 +1,13 @@ +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Webhooks +{ + public abstract class WebhookDefinitionProvider : ITransientDependency + { + /// + /// Used to add/manipulate webhook definitions. + /// + /// Context, + public abstract void Define(IWebhookDefinitionContext context); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs new file mode 100644 index 000000000..bd32fa76d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookEvent.cs @@ -0,0 +1,30 @@ +using System; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Store created web hooks. To see who get that webhook check with and you can get + /// + public class WebhookEvent + { + public Guid Id { get; set; } + + /// + /// Webhook unique name + /// + public string WebhookName { get; set; } + + /// + /// Webhook data as JSON string. + /// + 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; } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs new file mode 100644 index 000000000..ae58049ed --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookGroupDefinition.cs @@ -0,0 +1,101 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Volo.Abp; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks; + +public class WebhookGroupDefinition +{ + [NotNull] + public string Name { get; set; } + public Dictionary Properties { get; } + + private ILocalizableString _displayName; + public ILocalizableString DisplayName + { + get { + return _displayName; + } + set { + _displayName = value; + } + } + + public IReadOnlyList Webhooks => _webhooks.ToImmutableList(); + private readonly List _webhooks; + public object this[string name] { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + protected internal WebhookGroupDefinition( + string name, + ILocalizableString displayName = null) + { + Name = name; + DisplayName = displayName ?? new FixedLocalizableString(Name); + + Properties = new Dictionary(); + _webhooks = new List(); + } + + public virtual WebhookDefinition AddWebhook( + string name, + ILocalizableString displayName = null, + ILocalizableString description = null) + { + if (Webhooks.Any(hook => hook.Name.Equals(name))) + { + throw new AbpException($"There is already an existing webhook with name: {name} in group {Name}"); + } + + var webhook = new WebhookDefinition( + name, + displayName, + description + ); + + _webhooks.Add(webhook); + + return webhook; + } + + public virtual void AddWebhooks(params WebhookDefinition[] webhooks) + { + foreach (var webhook in webhooks) + { + if (Webhooks.Any(hook => hook.Name.Equals(webhook.Name))) + { + throw new AbpException($"There is already an existing webhook with name: {webhook.Name} in group {Name}"); + } + } + + _webhooks.AddRange(webhooks); + } + + + + [CanBeNull] + public WebhookDefinition GetWebhookOrNull([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + foreach (var webhook in Webhooks) + { + if (webhook.Name == name) + { + return webhook; + } + } + + return null; + } + + public override string ToString() + { + return $"[{nameof(WebhookGroupDefinition)} {Name}]"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs new file mode 100644 index 000000000..91442552f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookHeader.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookHeader + { + /// + /// If true, webhook will only contain given headers. If false given headers will be added to predefined headers in subscription. + /// Default is false + /// + public bool UseOnlyGivenHeaders { get; set; } + + /// + /// That headers will be sent with the webhook. + /// + public IDictionary Headers { get; set; } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs new file mode 100644 index 000000000..854795786 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookManager.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json; +using System; +using System.Globalization; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Webhooks +{ + public abstract class WebhookManager : IWebhookManager + { + private const string SignatureHeaderKey = "sha256"; + private const string SignatureHeaderValueTemplate = SignatureHeaderKey + "={0}"; + private const string SignatureHeaderName = "abp-webhook-signature"; + protected IWebhookSendAttemptStore WebhookSendAttemptStore { get; } + + protected WebhookManager( + IWebhookSendAttemptStore webhookSendAttemptStore) + { + WebhookSendAttemptStore = webhookSendAttemptStore; + } + + public virtual async Task GetWebhookPayloadAsync(WebhookSenderArgs webhookSenderArgs) + { + var data = JsonConvert.SerializeObject(webhookSenderArgs.Data); + + var attemptNumber = await WebhookSendAttemptStore.GetSendAttemptCountAsync( + webhookSenderArgs.TenantId, + webhookSenderArgs.WebhookEventId, + webhookSenderArgs.WebhookSubscriptionId); + + return new WebhookPayload( + webhookSenderArgs.WebhookEventId.ToString(), + webhookSenderArgs.WebhookName, + attemptNumber) + { + Data = data + }; + } + + public virtual void SignWebhookRequest(HttpRequestMessage request, string serializedBody, string secret) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (string.IsNullOrWhiteSpace(serializedBody)) + { + throw new ArgumentNullException(nameof(serializedBody)); + } + + request.Content = new StringContent(serializedBody, Encoding.UTF8, "application/json"); + + var secretBytes = Encoding.UTF8.GetBytes(secret); + var headerValue = string.Format(CultureInfo.InvariantCulture, SignatureHeaderValueTemplate, serializedBody.Sha256(secretBytes)); + + request.Headers.Add(SignatureHeaderName, headerValue); + } + + public virtual async Task GetSerializedBodyAsync(WebhookSenderArgs webhookSenderArgs) + { + if (webhookSenderArgs.SendExactSameData) + { + return webhookSenderArgs.Data; + } + + var payload = await GetWebhookPayloadAsync(webhookSenderArgs); + + var serializedBody = JsonConvert.SerializeObject(payload); + + return serializedBody; + } + + public abstract Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs); + + public abstract Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs new file mode 100644 index 000000000..ff287cbd0 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookPayload.cs @@ -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; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs new file mode 100644 index 000000000..8189f8f72 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSendAttempt.cs @@ -0,0 +1,39 @@ +using System; +using System.Net; + +namespace LINGYUN.Abp.Webhooks +{ + /// + /// Table for store webhook work items. Each item stores web hook send attempt of to subscribed tenants + /// + public class WebhookSendAttempt + { + public Guid Id { get; set; } + + /// + /// foreign id + /// + public Guid WebhookEventId { get; set; } + + /// + /// foreign id + /// + public Guid WebhookSubscriptionId { get; set; } + + /// + /// Webhook response content that webhook endpoint send back + /// + public string Response { get; set; } + + /// + /// Webhook response status code that webhook endpoint send back + /// + public HttpStatusCode? ResponseStatusCode { get; set; } + + public DateTime CreationTime { get; set; } + + public DateTime? LastModificationTime { get; set; } + + public Guid? TenantId { get; set; } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs new file mode 100644 index 000000000..cb30acbf9 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSenderArgs.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookSenderArgs + { + public Guid? TenantId { get; set; } + + //Webhook information + + /// + /// foreign id + /// + public Guid WebhookEventId { get; set; } + + /// + /// Webhook unique name + /// + public string WebhookName { get; set; } + + /// + /// Webhook data as JSON string. + /// + public string Data { get; set; } + + //Subscription information + + /// + /// foreign id + /// + public Guid WebhookSubscriptionId { get; set; } + + /// + /// Subscription webhook endpoint + /// + public string WebhookUri { get; set; } + + /// + /// Webhook secret + /// + public string Secret { get; set; } + + /// + /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. + /// + public IDictionary Headers { get; set; } + + /// + /// True: It sends the exact same data as the parameter to clients. + /// + /// False: It sends data in . It is recommended way. + /// + /// + public bool SendExactSameData { get; set; } + + public WebhookSenderArgs() + { + Headers = new Dictionary(); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs new file mode 100644 index 000000000..9e4e3bf2a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionInfo.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookSubscriptionInfo + { + public Guid Id { get; set; } + /// + /// Subscribed Tenant's id . + /// + public Guid? TenantId { get; set; } + + /// + /// Subscription webhook endpoint + /// + public string WebhookUri { get; set; } + + /// + /// Webhook secret + /// + public string Secret { get; set; } + + /// + /// Is subscription active + /// + public bool IsActive { get; set; } + + /// + /// Subscribed webhook definitions unique names.It contains webhook definitions list as json + /// + /// Do not change it manually. + /// Use , + /// , + /// and + /// to change it. + /// + /// + public List Webhooks { get; set; } + + /// + /// Gets a set of additional HTTP headers.That headers will be sent with the webhook. It contains webhook header dictionary as json + /// + /// Do not change it manually. + /// Use , + /// , + /// , + /// to change it. + /// + /// + public IDictionary Headers { get; set; } + + public WebhookSubscriptionInfo() + { + IsActive = true; + Headers = new Dictionary(); + Webhooks = new List(); + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs new file mode 100644 index 000000000..fe1a9c697 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/LINGYUN/Abp/Webhooks/WebhookSubscriptionManager.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Authorization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.Webhooks +{ + public class WebhookSubscriptionManager : IWebhookSubscriptionManager, ITransientDependency + { + public IWebhookSubscriptionsStore WebhookSubscriptionsStore { get; set; } + + private readonly IGuidGenerator _guidGenerator; + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IWebhookDefinitionManager _webhookDefinitionManager; + + private const string WebhookSubscriptionSecretPrefix = "whs_"; + + public WebhookSubscriptionManager( + IGuidGenerator guidGenerator, + IUnitOfWorkManager unitOfWorkManager, + IWebhookDefinitionManager webhookDefinitionManager) + { + _guidGenerator = guidGenerator; + _unitOfWorkManager = unitOfWorkManager; + _webhookDefinitionManager = webhookDefinitionManager; + + WebhookSubscriptionsStore = NullWebhookSubscriptionsStore.Instance; + } + + public virtual async Task GetAsync(Guid id) + { + return await WebhookSubscriptionsStore.GetAsync(id); + } + + public virtual async Task> GetAllSubscriptionsAsync(Guid? tenantId) + { + return await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId); + } + + public virtual async Task> GetAllSubscriptionsIfFeaturesGrantedAsync(Guid? tenantId, string webhookName) + { + if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) + { + return new List(); + } + + return (await WebhookSubscriptionsStore.GetAllSubscriptionsAsync(tenantId, webhookName)).ToList(); + } + + public virtual async Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) + { + return (await WebhookSubscriptionsStore.GetAllSubscriptionsOfTenantsAsync(tenantIds)).ToList(); + } + + public virtual async Task> GetAllSubscriptionsOfTenantsIfFeaturesGrantedAsync(Guid?[] tenantIds, string webhookName) + { + var featureGrantedTenants = new List(); + 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 IsSubscribedAsync(Guid? tenantId, string webhookName) + { + if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) + { + return false; + } + + return await WebhookSubscriptionsStore.IsSubscribedAsync(tenantId, webhookName); + } + + public virtual async Task AddOrUpdateSubscriptionAsync(WebhookSubscriptionInfo webhookSubscription) + { + using (var uow = _unitOfWorkManager.Begin()) + { + await CheckIfPermissionsGrantedAsync(webhookSubscription); + + if (webhookSubscription.Id == default) + { + webhookSubscription.Id = _guidGenerator.Create(); + webhookSubscription.Secret = WebhookSubscriptionSecretPrefix + Guid.NewGuid().ToString("N"); + await WebhookSubscriptionsStore.InsertAsync(webhookSubscription); + } + else + { + await WebhookSubscriptionsStore.UpdateAsync(webhookSubscription); + } + + await uow.SaveChangesAsync(); + } + } + + public virtual async Task ActivateWebhookSubscriptionAsync(Guid id, bool active) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var webhookSubscription = await WebhookSubscriptionsStore.GetAsync(id); + webhookSubscription.IsActive = active; + + await uow.SaveChangesAsync(); + } + } + + public virtual async Task DeleteSubscriptionAsync(Guid id) + { + using (var uow = _unitOfWorkManager.Begin()) + { + await WebhookSubscriptionsStore.DeleteAsync(id); + + await uow.SaveChangesAsync(); + } + } + + public virtual async Task AddWebhookAsync(WebhookSubscriptionInfo subscription, string webhookName) + { + using (var uow = _unitOfWorkManager.Begin()) + { + await CheckPermissionsAsync(subscription.TenantId, webhookName); + webhookName = webhookName.Trim(); + if (webhookName.IsNullOrWhiteSpace()) + { + throw new ArgumentNullException(nameof(webhookName), $"{nameof(webhookName)} can not be null, empty or whitespace!"); + } + + if (!subscription.Webhooks.Contains(webhookName)) + { + subscription.Webhooks.Add(webhookName); + + await WebhookSubscriptionsStore.UpdateAsync(subscription); + } + + await uow.SaveChangesAsync(); + } + } + + #region PermissionCheck + + protected virtual async Task CheckIfPermissionsGrantedAsync(WebhookSubscriptionInfo webhookSubscription) + { + if (webhookSubscription.Webhooks.IsNullOrEmpty()) + { + return; + } + + foreach (var webhookDefinition in webhookSubscription.Webhooks) + { + await CheckPermissionsAsync(webhookSubscription.TenantId, webhookDefinition); + } + } + + protected virtual async Task CheckPermissionsAsync(Guid? tenantId, string webhookName) + { + if (!await _webhookDefinitionManager.IsAvailableAsync(tenantId, webhookName)) + { + throw new AbpAuthorizationException($"Tenant \"{tenantId}\" must have necessary feature(s) to use webhook \"{webhookName}\""); + } + } + + #endregion + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs new file mode 100644 index 000000000..f4202f6e2 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebHooks/System/AbpStringCryptographyExtensions.cs @@ -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)); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/FodyWeavers.xml new file mode 100644 index 000000000..c485a4548 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN.Abp.Webhooks.Identity.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN.Abp.Webhooks.Identity.csproj new file mode 100644 index 000000000..32c987515 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN.Abp.Webhooks.Identity.csproj @@ -0,0 +1,30 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/AbpWebhooksIdentityModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/AbpWebhooksIdentityModule.cs new file mode 100644 index 000000000..0ca523251 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/AbpWebhooksIdentityModule.cs @@ -0,0 +1,32 @@ +using Volo.Abp.Domain; +using Volo.Abp.EventBus; +using Volo.Abp.Identity; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Users; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Webhooks.Identity; + +[DependsOn(typeof(AbpDddDomainModule))] +[DependsOn(typeof(AbpEventBusModule))] +[DependsOn(typeof(AbpUsersAbstractionModule))] +[DependsOn(typeof(AbpIdentityDomainSharedModule))] +public class AbpWebhooksIdentityModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/Webhooks/Identity/Localization/Resources"); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleNameChangedWto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleNameChangedWto.cs new file mode 100644 index 000000000..c91615749 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleNameChangedWto.cs @@ -0,0 +1,13 @@ +using System; + +namespace LINGYUN.Abp.Webhooks.Identity; + +[Serializable] +public class IdentityRoleNameChangedWto +{ + public Guid Id { get; set; } + + public string Name { get; set; } + + public string OldName { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleWebHooker.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleWebHooker.cs new file mode 100644 index 000000000..5d60012c0 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleWebHooker.cs @@ -0,0 +1,63 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Identity; + +namespace LINGYUN.Abp.Webhooks.Identity; + +public class IdentityRoleWebhooker : + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler, + ITransientDependency +{ + private readonly IWebhookPublisher _webhookPublisher; + + public IdentityRoleWebhooker( + IWebhookPublisher webhookPublisher) + { + _webhookPublisher = webhookPublisher; + } + + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityRole.Create, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityRole.Update, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityRole.Delete, eventData.Entity); + } + + public async virtual Task HandleEventAsync(IdentityRoleNameChangedEto eventData) + { + await _webhookPublisher.PublishAsync( + IdentityWebhookNames.IdentityRole.ChangeName, + new IdentityRoleNameChangedWto + { + Id = eventData.Id, + Name = eventData.Name, + OldName = eventData.OldName, + }, + eventData.TenantId); + } + + protected async virtual Task PublishAsync(string webhookName, IdentityRoleEto eto) + { + await _webhookPublisher.PublishAsync( + webhookName, + new IdentityRoleWto + { + Id = eto.Id, + Name = eto.Name + }, + eto.TenantId); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleWto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleWto.cs new file mode 100644 index 000000000..1271a5877 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityRoleWto.cs @@ -0,0 +1,11 @@ +using System; + +namespace LINGYUN.Abp.Webhooks.Identity; + +[Serializable] +public class IdentityRoleWto +{ + public Guid Id { get; set; } + + public string Name { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityUserWebHooker.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityUserWebHooker.cs new file mode 100644 index 000000000..7dc188cf5 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityUserWebHooker.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Users; + +namespace LINGYUN.Abp.Webhooks.Identity; + +public class IdentityUserWebhooker : + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency +{ + private readonly IWebhookPublisher _webhookPublisher; + + public IdentityUserWebhooker( + IWebhookPublisher webhookPublisher) + { + _webhookPublisher = webhookPublisher; + } + + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityUser.Create, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityUser.Update, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityUser.Delete, eventData.Entity); + } + + protected async virtual Task PublishAsync(string webhookName, UserEto eto) + { + await _webhookPublisher.PublishAsync( + webhookName, + new IdentityUserWto + { + Id = eto.Id, + Name = eto.Name, + Email = eto.Email, + EmailConfirmed = eto.EmailConfirmed, + UserName = eto.UserName, + PhoneNumber = eto.PhoneNumber, + PhoneNumberConfirmed = eto.PhoneNumberConfirmed, + Surname = eto.Surname, + }, + eto.TenantId); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityUserWto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityUserWto.cs new file mode 100644 index 000000000..29da00874 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityUserWto.cs @@ -0,0 +1,23 @@ +using System; + +namespace LINGYUN.Abp.Webhooks.Identity; + +[Serializable] +public class IdentityUserWto +{ + public Guid Id { get; set; } + + public string UserName { get; set; } + + public string Name { get; set; } + + public string Surname { get; set; } + + public string Email { get; set; } + + public bool EmailConfirmed { get; set; } + + public string PhoneNumber { get; set; } + + public bool PhoneNumberConfirmed { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityWebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityWebhookDefinitionProvider.cs new file mode 100644 index 000000000..6c243b942 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityWebhookDefinitionProvider.cs @@ -0,0 +1,64 @@ +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks.Identity; + +public class IdentityWebhookDefinitionProvider : WebhookDefinitionProvider +{ + public override void Define(IWebhookDefinitionContext context) + { + var identityGroup = context.AddGroup( + IdentityWebhookNames.GroupName, + L("Webhooks:Identity")); + + identityGroup.AddWebhooks(CreateIdentityRoleWebhooks()); + identityGroup.AddWebhooks(CreateIdentityUserWebhooks()); + } + + protected virtual WebhookDefinition[] CreateIdentityRoleWebhooks() + { + return new[] + { + new WebhookDefinition( + IdentityWebhookNames.IdentityRole.Create, + L("Webhooks:CreateRole"), + L("Webhooks:CreateRoleDesc")), + new WebhookDefinition( + IdentityWebhookNames.IdentityRole.Update, + L("Webhooks:UpdateRole"), + L("Webhooks:UpdateRoleDesc")), + new WebhookDefinition( + IdentityWebhookNames.IdentityRole.Delete, + L("Webhooks:DeleteRole"), + L("Webhooks:DeleteRoleDesc")), + new WebhookDefinition( + IdentityWebhookNames.IdentityRole.ChangeName, + L("Webhooks:ChangeRoleName"), + L("Webhooks:ChangeRoleNameDesc")), + }; + } + + protected virtual WebhookDefinition[] CreateIdentityUserWebhooks() + { + return new[] + { + new WebhookDefinition( + IdentityWebhookNames.IdentityUser.Create, + L("Webhooks:CreateUser"), + L("Webhooks:CreateUserDesc")), + new WebhookDefinition( + IdentityWebhookNames.IdentityUser.Update, + L("Webhooks:UpdateUser"), + L("Webhooks:UpdateUserDesc")), + new WebhookDefinition( + IdentityWebhookNames.IdentityUser.Delete, + L("Webhooks:DeleteUser"), + L("Webhooks:DeleteUserDesc")), + }; + } + + private static ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityWebhookNames.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityWebhookNames.cs new file mode 100644 index 000000000..be7fa0232 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/IdentityWebhookNames.cs @@ -0,0 +1,22 @@ +namespace LINGYUN.Abp.Webhooks.Identity; + +public static class IdentityWebhookNames +{ + public const string GroupName = "abp.webhooks.identity"; + public static class IdentityRole + { + public const string Prefix = GroupName + ".roles"; + public const string Create = Prefix + ".create"; + public const string Update = Prefix + ".update"; + public const string Delete = Prefix + ".delete"; + public const string ChangeName = Prefix + ".change_name"; + } + + public static class IdentityUser + { + public const string Prefix = GroupName + ".users"; + public const string Create = Prefix + ".create"; + public const string Update = Prefix + ".update"; + public const string Delete = Prefix + ".delete"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/Localization/Resources/en.json b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/Localization/Resources/en.json new file mode 100644 index 000000000..05df4003d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/Localization/Resources/en.json @@ -0,0 +1,20 @@ +{ + "culture": "en", + "texts": { + "Webhooks:Identity": "Identity", + "Webhooks:CreateRole": "Create Role", + "Webhooks:CreateRoleDesc": "A new role has been created", + "Webhooks:UpdateRole": "Update Role", + "Webhooks:UpdateRoleDesc": "A role has changed", + "Webhooks:DeleteRole": "Delete Role", + "Webhooks:DeleteRoleDesc": "Deleted Role", + "Webhooks:ChangeRoleName": "Change Role Name", + "Webhooks:ChangeRoleNameDesc": "The name of a role was changed", + "Webhooks:CreateUser": "Create User", + "Webhooks:CreateUserDesc": "A new user has been created", + "Webhooks:UpdateUser": "Update User", + "Webhooks:UpdateUserDesc": "A user has been changed", + "Webhooks:DeleteUser": "Delete User", + "Webhooks:DeleteUserDesc": "Deleted User" + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/Localization/Resources/zh-Hans.json b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..eb06d0489 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/Localization/Resources/zh-Hans.json @@ -0,0 +1,20 @@ +{ + "culture": "zh-Hans", + "texts": { + "Webhooks:Identity": "身份认证", + "Webhooks:CreateRole": "创建角色", + "Webhooks:CreateRoleDesc": "一个新角色已创建", + "Webhooks:UpdateRole": "编辑角色", + "Webhooks:UpdateRoleDesc": "一个角色属性已变更", + "Webhooks:DeleteRole": "删除角色", + "Webhooks:DeleteRoleDesc": "已删除角色", + "Webhooks:ChangeRoleName": "改变角色名称", + "Webhooks:ChangeRoleNameDesc": "一个角色的名称被更改", + "Webhooks:CreateUser": "创建用户", + "Webhooks:CreateUserDesc": "一个新用户已创建", + "Webhooks:UpdateUser": "编辑用户", + "Webhooks:UpdateUserDesc": "一个用户属性已变更", + "Webhooks:DeleteUser": "删除用户", + "Webhooks:DeleteUserDesc": "已删除用户" + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/OrganizationUnitWebHooker.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/OrganizationUnitWebHooker.cs new file mode 100644 index 000000000..a259bc16b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/OrganizationUnitWebHooker.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Identity; + +namespace LINGYUN.Abp.Webhooks.Identity; + +public class OrganizationUnitWebhooker : + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency +{ + private readonly IWebhookPublisher _webhookPublisher; + + public OrganizationUnitWebhooker( + IWebhookPublisher webhookPublisher) + { + _webhookPublisher = webhookPublisher; + } + + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityRole.Create, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityRole.Update, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + await PublishAsync(IdentityWebhookNames.IdentityRole.Delete, eventData.Entity); + } + + protected async virtual Task PublishAsync(string webhookName, OrganizationUnitEto eto) + { + await _webhookPublisher.PublishAsync( + webhookName, + new OrganizationUnitWto + { + Id = eto.Id, + DisplayName = eto.DisplayName + }, + eto.TenantId); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/OrganizationUnitWto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/OrganizationUnitWto.cs new file mode 100644 index 000000000..3dd758890 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Identity/LINGYUN/Abp/Webhooks/Identity/OrganizationUnitWto.cs @@ -0,0 +1,13 @@ +using System; + +namespace LINGYUN.Abp.Webhooks.Identity; + +[Serializable] +public class OrganizationUnitWto +{ + public Guid Id { get; set; } + + public string Code { get; set; } + + public string DisplayName { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/FodyWeavers.xml new file mode 100644 index 000000000..c485a4548 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN.Abp.Webhooks.Saas.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN.Abp.Webhooks.Saas.csproj new file mode 100644 index 000000000..66d9fa1b8 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN.Abp.Webhooks.Saas.csproj @@ -0,0 +1,30 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/AbpWebhooksSaasModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/AbpWebhooksSaasModule.cs new file mode 100644 index 000000000..93567e45a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/AbpWebhooksSaasModule.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.Saas; +using LINGYUN.Abp.Saas.Localization; +using Volo.Abp.Domain; +using Volo.Abp.EventBus; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Webhooks.Saas; + +[DependsOn(typeof(AbpDddDomainModule))] +[DependsOn(typeof(AbpEventBusModule))] +[DependsOn(typeof(AbpSaasDomainSharedModule))] +public class AbpWebhooksSaasModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/Webhooks/Saas/Localization/Resources"); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/EditionWebhooker.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/EditionWebhooker.cs new file mode 100644 index 000000000..fffdd0328 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/EditionWebhooker.cs @@ -0,0 +1,48 @@ +using LINGYUN.Abp.Saas.Editions; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; + +namespace LINGYUN.Abp.Webhooks.Saas; + +public class EditionWebhooker : + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency +{ + private readonly IWebhookPublisher _webhookPublisher; + + public EditionWebhooker( + IWebhookPublisher webhookPublisher) + { + _webhookPublisher = webhookPublisher; + } + + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + await PublishAsync(SaasWebhookNames.Edition.Create, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) + { + await PublishAsync(SaasWebhookNames.Edition.Update, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + await PublishAsync(SaasWebhookNames.Edition.Delete, eventData.Entity); + } + + protected async virtual Task PublishAsync(string webhookName, EditionEto eto) + { + await _webhookPublisher.PublishAsync( + webhookName, + new EditionWto + { + Id = eto.Id, + DisplayName = eto.DisplayName + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/EditionWto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/EditionWto.cs new file mode 100644 index 000000000..8a3a1208b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/EditionWto.cs @@ -0,0 +1,11 @@ +using System; + +namespace LINGYUN.Abp.Webhooks.Saas; + +[Serializable] +public class EditionWto +{ + public Guid Id { get; set; } + + public string DisplayName { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/Localization/Resources/en.json b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/Localization/Resources/en.json new file mode 100644 index 000000000..fdafcd2d1 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/Localization/Resources/en.json @@ -0,0 +1,18 @@ +{ + "culture": "en", + "texts": { + "Webhooks:Saas": "Saas", + "Webhooks:CreateEdition": "Create Edition", + "Webhooks:CreateEditionDesc": "A new Edition has been created", + "Webhooks:UpdateEdition": "Update Edition", + "Webhooks:UpdateEditionDesc": "A Edition has changed", + "Webhooks:DeleteEdition": "Delete Edition", + "Webhooks:DeleteEditionDesc": "Deleted Edition", + "Webhooks:CreateTenant": "Create Tenant", + "Webhooks:CreateTenantDesc": "A new Tenant has been created", + "Webhooks:UpdateTenant": "Update Tenant", + "Webhooks:UpdateTenantDesc": "A Tenant has been changed", + "Webhooks:DeleteTenant": "Delete Tenant", + "Webhooks:DeleteTenantDesc": "Deleted Tenant" + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/Localization/Resources/zh-Hans.json b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..fd5ddbf30 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/Localization/Resources/zh-Hans.json @@ -0,0 +1,18 @@ +{ + "culture": "zh-Hans", + "texts": { + "Webhooks:Saas": "Saas", + "Webhooks:CreateEdition": "创建版本", + "Webhooks:CreateEditionDesc": "一个新版本已创建", + "Webhooks:UpdateEdition": "编辑版本", + "Webhooks:UpdateEditionDesc": "一个版本属性已变更", + "Webhooks:DeleteEdition": "删除版本", + "Webhooks:DeleteEditionDesc": "已删除版本", + "Webhooks:CreateTenant": "创建租户", + "Webhooks:CreateTenantDesc": "一个新租户已创建", + "Webhooks:UpdateTenant": "编辑租户", + "Webhooks:UpdateTenantDesc": "一个租户属性已变更", + "Webhooks:DeleteTenant": "删除租户", + "Webhooks:DeleteTenantDesc": "已删除租户" + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/SaasWebhookDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/SaasWebhookDefinitionProvider.cs new file mode 100644 index 000000000..b2624f179 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/SaasWebhookDefinitionProvider.cs @@ -0,0 +1,60 @@ +using LINGYUN.Abp.Saas.Localization; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Webhooks.Saas; + +public class SaasWebhookDefinitionProvider : WebhookDefinitionProvider +{ + public override void Define(IWebhookDefinitionContext context) + { + var saasGroup = context.AddGroup( + SaasWebhookNames.GroupName, + L("Webhooks:Saas")); + + saasGroup.AddWebhooks(CreateEditionWebhooks()); + saasGroup.AddWebhooks(CreateTenantWebhooks()); + } + + protected virtual WebhookDefinition[] CreateEditionWebhooks() + { + return new[] + { + new WebhookDefinition( + SaasWebhookNames.Edition.Create, + L("Webhooks:CreateEdition"), + L("Webhooks:CreateEditionDesc")), + new WebhookDefinition( + SaasWebhookNames.Edition.Update, + L("Webhooks:UpdateEdition"), + L("Webhooks:UpdateEditionDesc")), + new WebhookDefinition( + SaasWebhookNames.Edition.Delete, + L("Webhooks:DeleteEdition"), + L("Webhooks:DeleteEditionDesc")), + }; + } + + protected virtual WebhookDefinition[] CreateTenantWebhooks() + { + return new[] + { + new WebhookDefinition( + SaasWebhookNames.Tenant.Create, + L("Webhooks:CreateTenant"), + L("Webhooks:CreateTenantDesc")), + new WebhookDefinition( + SaasWebhookNames.Tenant.Update, + L("Webhooks:UpdateTenant"), + L("Webhooks:UpdateTenantDesc")), + new WebhookDefinition( + SaasWebhookNames.Tenant.Delete, + L("Webhooks:DeleteTenant"), + L("Webhooks:DeleteTenantDesc")), + }; + } + + private static ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/SaasWebhookNames.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/SaasWebhookNames.cs new file mode 100644 index 000000000..399903fc6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/SaasWebhookNames.cs @@ -0,0 +1,21 @@ +namespace LINGYUN.Abp.Webhooks.Saas; + +public static class SaasWebhookNames +{ + public const string GroupName = "abp.webhooks.saas"; + public static class Edition + { + public const string Prefix = GroupName + ".editions"; + public const string Create = Prefix + ".create"; + public const string Update = Prefix + ".update"; + public const string Delete = Prefix + ".delete"; + } + + public static class Tenant + { + public const string Prefix = GroupName + ".tenants"; + public const string Create = Prefix + ".create"; + public const string Update = Prefix + ".update"; + public const string Delete = Prefix + ".delete"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/TenantWebhooker.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/TenantWebhooker.cs new file mode 100644 index 000000000..916fd8f3b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/TenantWebhooker.cs @@ -0,0 +1,48 @@ +using LINGYUN.Abp.Saas.Tenants; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; + +namespace LINGYUN.Abp.Webhooks.Saas; + +public class TenantWebhooker : + IDistributedEventHandler>, + IDistributedEventHandler>, + IDistributedEventHandler>, + ITransientDependency +{ + private readonly IWebhookPublisher _webhookPublisher; + + public TenantWebhooker( + IWebhookPublisher webhookPublisher) + { + _webhookPublisher = webhookPublisher; + } + + public async virtual Task HandleEventAsync(EntityCreatedEto eventData) + { + await PublishAsync(SaasWebhookNames.Tenant.Create, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) + { + await PublishAsync(SaasWebhookNames.Tenant.Update, eventData.Entity); + } + + public async virtual Task HandleEventAsync(EntityDeletedEto eventData) + { + await PublishAsync(SaasWebhookNames.Tenant.Delete, eventData.Entity); + } + + protected async virtual Task PublishAsync(string webhookName, TenantEto eto) + { + await _webhookPublisher.PublishAsync( + webhookName, + new TenantWto + { + Id = eto.Id, + Name = eto.Name + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/TenantWto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/TenantWto.cs new file mode 100644 index 000000000..ba2d4ef0d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Saas/LINGYUN/Abp/Webhooks/Saas/TenantWto.cs @@ -0,0 +1,12 @@ +using System; + +namespace LINGYUN.Abp.Webhooks.Saas; + +[Serializable] +public class TenantWto +{ + public Guid Id { get; set; } + + public string Name { get; set; } +} + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj new file mode 100644 index 000000000..70d317b54 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN.Abp.WebhooksManagement.Application.Contracts.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Authorization/WebhooksManagementPermissionDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Authorization/WebhooksManagementPermissionDefinitionProvider.cs new file mode 100644 index 000000000..4c9513f02 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Authorization/WebhooksManagementPermissionDefinitionProvider.cs @@ -0,0 +1,62 @@ +using LINGYUN.Abp.WebhooksManagement.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.WebhooksManagement.Authorization; + +public class WebhooksManagementPermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var group = context.AddGroup( + WebhooksManagementPermissions.GroupName, + L("Permission:WebhooksManagement"), + MultiTenancySides.Host); + + var subscription = group.AddPermission( + WebhooksManagementPermissions.WebhookSubscription.Default, + L("Permission:Subscriptions"), + MultiTenancySides.Host) + .WithProviders(ClientPermissionValueProvider.ProviderName); + subscription.AddChild( + WebhooksManagementPermissions.WebhookSubscription.Create, + L("Permission:Create"), + MultiTenancySides.Host) + .WithProviders(ClientPermissionValueProvider.ProviderName); + subscription.AddChild( + WebhooksManagementPermissions.WebhookSubscription.Update, + L("Permission:Update"), + MultiTenancySides.Host) + .WithProviders(ClientPermissionValueProvider.ProviderName); + subscription.AddChild( + WebhooksManagementPermissions.WebhookSubscription.Delete, + L("Permission:Delete"), + MultiTenancySides.Host) + .WithProviders(ClientPermissionValueProvider.ProviderName); + + var sendAttempts = group.AddPermission( + WebhooksManagementPermissions.WebhooksSendAttempts.Default, + L("Permission:SendAttempts"), + MultiTenancySides.Host); + sendAttempts.AddChild( + WebhooksManagementPermissions.WebhooksSendAttempts.Resend, + L("Permission:Resend"), + MultiTenancySides.Host); + + group.AddPermission( + WebhooksManagementPermissions.Publish, + L("Permission:Publish")) + .WithProviders(ClientPermissionValueProvider.ProviderName); + + group.AddPermission( + WebhooksManagementPermissions.ManageSettings, + L("Permission:ManageSettings"), + MultiTenancySides.Host); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Authorization/WebhooksManagementPermissions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Authorization/WebhooksManagementPermissions.cs new file mode 100644 index 000000000..6f6ab9c47 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Authorization/WebhooksManagementPermissions.cs @@ -0,0 +1,27 @@ +namespace LINGYUN.Abp.WebhooksManagement.Authorization; + +public static class WebhooksManagementPermissions +{ + public const string GroupName = "AbpWebhooks"; + + /// + /// 授权允许发布Webhooks事件, 建议客户端授权 + /// + public const string Publish = GroupName + ".Publish"; + + public const string ManageSettings = GroupName + ".ManageSettings"; + + public static class WebhookSubscription + { + public const string Default = GroupName + ".Subscriptions"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } + + public static class WebhooksSendAttempts + { + public const string Default = GroupName + ".SendAttempts"; + public const string Resend = Default + ".Resend"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Features/WebhooksManagementFeatureDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Features/WebhooksManagementFeatureDefinitionProvider.cs new file mode 100644 index 000000000..d124abe45 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Features/WebhooksManagementFeatureDefinitionProvider.cs @@ -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(name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Features/WebhooksManagementFeatureNames.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Features/WebhooksManagementFeatureNames.cs new file mode 100644 index 000000000..6cb71bf6d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/Features/WebhooksManagementFeatureNames.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.WebhooksManagement.Features; + +public static class WebhooksManagementFeatureNames +{ + public const string GroupName = "WebhooksManagement"; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookPublishAppService.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookPublishAppService.cs new file mode 100644 index 000000000..1e375d98f --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookPublishAppService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.WebhooksManagement; + +public interface IWebhookPublishAppService : IApplicationService +{ + Task PublishAsync(WebhookPublishInput input); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookSendRecordAppService.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookSendRecordAppService.cs new file mode 100644 index 000000000..da24b710b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookSendRecordAppService.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.WebhooksManagement; + +public interface IWebhookSendRecordAppService : IApplicationService +{ + Task GetAsync(Guid id); + + Task ResendAsync(Guid id); + + Task> GetListAsync(WebhookSendRecordGetListInput input); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookSubscriptionAppService.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookSubscriptionAppService.cs new file mode 100644 index 000000000..132fcbd73 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/IWebhookSubscriptionAppService.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.WebhooksManagement; + +public interface IWebhookSubscriptionAppService : + ICrudAppService< + WebhookSubscriptionDto, + Guid, + WebhookSubscriptionGetListInput, + WebhookSubscriptionCreateInput, + WebhookSubscriptionUpdateInput> +{ + Task> GetAllAvailableWebhooksAsync(); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookAvailableDto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookAvailableDto.cs new file mode 100644 index 000000000..0e4c5ed6d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookAvailableDto.cs @@ -0,0 +1,8 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookAvailableDto +{ + public string Name { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookAvailableGroupDto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookAvailableGroupDto.cs new file mode 100644 index 000000000..37a3c8fe1 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookAvailableGroupDto.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookAvailableGroupDto +{ + public string Name { get; set; } + public string DisplayName { get; set; } + public List Webhooks { get; set; } = new List(); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookEventRecordDto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookEventRecordDto.cs new file mode 100644 index 000000000..a282777a6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookEventRecordDto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookEventRecordDto : EntityDto +{ + public Guid? TenantId { get; set; } + public string WebhookName { get; set; } + public string Data { get; set; } + public DateTime CreationTime { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookPublishInput.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookPublishInput.cs new file mode 100644 index 000000000..e27013228 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookPublishInput.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookPublishInput +{ + [Required] + [DynamicStringLength(typeof(WebhookEventRecordConsts), nameof(WebhookEventRecordConsts.MaxWebhookNameLength))] + public string WebhookName { get; set; } + + [Required] + [DynamicStringLength(typeof(WebhookEventRecordConsts), nameof(WebhookEventRecordConsts.MaxDataLength))] + public string Data { get; set; } + + public bool SendExactSameData { get; set; } + + public WebhooksHeaderInput Header { get; set; } = new WebhooksHeaderInput(); + + public List TenantIds { get; set; } = new List(); +} + +public class WebhooksHeaderInput +{ + public bool UseOnlyGivenHeaders { get; set; } + + public IDictionary Headers { get; set; } = new Dictionary(); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordDto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordDto.cs new file mode 100644 index 000000000..9c0dcd85e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordDto.cs @@ -0,0 +1,24 @@ +using System; +using System.Net; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSendRecordDto : EntityDto +{ + public Guid? TenantId { get; set; } + + public Guid WebhookEventId { get; set; } + + public Guid WebhookSubscriptionId { get; set; } + + public string Response { get; set; } + + public HttpStatusCode? ResponseStatusCode { get; set; } + + public DateTime CreationTime { get; set; } + + public DateTime? LastModificationTime { get; set; } + + public WebhookEventRecordDto WebhookEvent { get; set; } = new WebhookEventRecordDto(); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordGetListInput.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordGetListInput.cs new file mode 100644 index 000000000..018cec849 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordGetListInput.cs @@ -0,0 +1,20 @@ +using System; +using System.Net; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSendRecordGetListInput : PagedAndSortedResultRequestDto +{ + public string Filter { get; set; } + + public Guid? WebhookEventId { get; set; } + + public Guid? SubscriptionId { get; set; } + + public HttpStatusCode? ResponseStatusCode { get; set; } + + public DateTime? BeginCreationTime { get; set; } + + public DateTime? EndCreationTime { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionCreateOrUpdateInput.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionCreateOrUpdateInput.cs new file mode 100644 index 000000000..94897417d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionCreateOrUpdateInput.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSubscriptionCreateInput : WebhookSubscriptionCreateOrUpdateInput +{ + +} + +public class WebhookSubscriptionUpdateInput : WebhookSubscriptionCreateOrUpdateInput +{ + +} + +public abstract class WebhookSubscriptionCreateOrUpdateInput +{ + [Required] + [DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxWebhookUriLength))] + public string WebhookUri { get; set; } + + [Required] + [DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxSecretLength))] + public string Secret { get; set; } + + public bool IsActive { get; set; } + + public List Webhooks { get; set; } = new List(); + + public Dictionary Headers { get; set; } = new Dictionary(); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionDto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionDto.cs new file mode 100644 index 000000000..204ffd2f9 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSubscriptionDto : CreationAuditedEntityDto +{ + public Guid? TenantId { get; set; } + public string WebhookUri { get; set; } + public string Secret { get; set; } + public bool IsActive { get; set; } + public List Webhooks { get; set; } = new List(); + public IDictionary Headers { get; set; } = new Dictionary(); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionGetListInput.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionGetListInput.cs new file mode 100644 index 000000000..9e9060184 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionGetListInput.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSubscriptionGetListInput : PagedAndSortedResultRequestDto +{ + public string Filter { get; set; } + + public Guid? TenantId { get; set; } + + [DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxWebhookUriLength))] + public string WebhookUri { get; set; } + + [DynamicStringLength(typeof(WebhookSubscriptionConsts), nameof(WebhookSubscriptionConsts.MaxSecretLength))] + public string Secret { get; set; } + + public bool? IsActive { get; set; } + + public string Webhooks { get; set; } + + public DateTime? BeginCreationTime { get; set; } + + public DateTime? EndCreationTime { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationContractsModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationContractsModule.cs new file mode 100644 index 000000000..977589b9c --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationContractsModule.cs @@ -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 +{ +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhooksManagementRemoteServiceConsts.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhooksManagementRemoteServiceConsts.cs new file mode 100644 index 000000000..3d5a2b9f6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application.Contracts/LINGYUN/Abp/WebhooksManagement/WebhooksManagementRemoteServiceConsts.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public static class WebhooksManagementRemoteServiceConsts +{ + public const string RemoteServiceName = "WebhooksManagement"; + public const string ModuleName = "webhooks-management"; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN.Abp.WebhooksManagement.Application.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN.Abp.WebhooksManagement.Application.csproj new file mode 100644 index 000000000..24d0a91e7 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN.Abp.WebhooksManagement.Application.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionExtensions.cs new file mode 100644 index 000000000..49c190f0a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionExtensions.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using System.Linq; + +namespace LINGYUN.Abp.WebhooksManagement.Extensions +{ + public static class WebhookSubscriptionExtensions + { + public static WebhookSubscriptionDto ToWebhookSubscriptionDto(this WebhookSubscription webhookSubscription) + { + return new WebhookSubscriptionDto + { + Id = webhookSubscription.Id, + TenantId = webhookSubscription.TenantId, + IsActive = webhookSubscription.IsActive, + Secret = webhookSubscription.Secret, + WebhookUri = webhookSubscription.WebhookUri, + Webhooks = webhookSubscription.GetSubscribedWebhooks(), + Headers = webhookSubscription.GetWebhookHeaders(), + CreationTime = webhookSubscription.CreationTime, + CreatorId = webhookSubscription.CreatorId + }; + } + + public static string ToSubscribedWebhooksString(this WebhookSubscriptionUpdateInput webhookSubscription) + { + if (webhookSubscription.Webhooks.Any()) + { + return JsonConvert.SerializeObject(webhookSubscription.Webhooks); + } + + return null; + } + + public static string ToWebhookHeadersString(this WebhookSubscriptionUpdateInput webhookSubscription) + { + if (webhookSubscription.Headers.Any()) + { + return JsonConvert.SerializeObject(webhookSubscription.Headers); + } + + return null; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookPublishAppService.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookPublishAppService.cs new file mode 100644 index 000000000..c6d637e77 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookPublishAppService.cs @@ -0,0 +1,45 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.WebhooksManagement.Authorization; +using Microsoft.AspNetCore.Authorization; +using Newtonsoft.Json; +using System.Linq; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.WebhooksManagement; + +[Authorize(WebhooksManagementPermissions.Publish)] +public class WebhookPublishAppService : WebhooksManagementAppServiceBase, IWebhookPublishAppService +{ + protected IWebhookPublisher InnerPublisher { get; } + + public WebhookPublishAppService(IWebhookPublisher innerPublisher) + { + InnerPublisher = innerPublisher; + } + + public async virtual Task PublishAsync(WebhookPublishInput input) + { + var webhookHeader = new WebhookHeader + { + UseOnlyGivenHeaders = input.Header.UseOnlyGivenHeaders, + Headers = input.Header.Headers, + }; + var inputData = JsonConvert.DeserializeObject(input.Data); + + if (input.TenantIds.Any()) + { + await InnerPublisher.PublishAsync( + input.TenantIds.ToArray(), + input.WebhookName, + inputData, + input.SendExactSameData, + webhookHeader); + return; + } + await InnerPublisher.PublishAsync( + input.WebhookName, + inputData, + input.SendExactSameData, + webhookHeader); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordAppService.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordAppService.cs new file mode 100644 index 000000000..77683b17c --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordAppService.cs @@ -0,0 +1,74 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.WebhooksManagement.Authorization; +using LINGYUN.Abp.WebhooksManagement.Extensions; +using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.BackgroundJobs; + +namespace LINGYUN.Abp.WebhooksManagement; + +[Authorize(WebhooksManagementPermissions.WebhooksSendAttempts.Default)] +public class WebhookSendRecordAppService : WebhooksManagementAppServiceBase, IWebhookSendRecordAppService +{ + protected IBackgroundJobManager BackgroundJobManager => LazyServiceProvider.LazyGetRequiredService(); + protected IWebhookEventRecordRepository EventRepository => LazyServiceProvider.LazyGetRequiredService(); + protected IWebhookSubscriptionRepository SubscriptionRepository => LazyServiceProvider.LazyGetRequiredService(); + + + protected IWebhookSendRecordRepository RecordRepository { get; } + + public WebhookSendRecordAppService( + IWebhookSendRecordRepository recordRepository) + { + RecordRepository = recordRepository; + } + + public async virtual Task GetAsync(Guid id) + { + var sendRecord = await RecordRepository.GetAsync(id); + + return ObjectMapper.Map(sendRecord); + } + + public async virtual Task> GetListAsync(WebhookSendRecordGetListInput input) + { + var filter = new WebhookSendRecordFilter + { + SubscriptionId = input.SubscriptionId, + ResponseStatusCode = input.ResponseStatusCode, + BeginCreationTime = input.BeginCreationTime, + EndCreationTime = input.EndCreationTime, + WebhookEventId = input.WebhookEventId, + Filter = input.Filter + }; + var totalCount = await RecordRepository.GetCountAsync(filter); + var sendRecords = await RecordRepository.GetListAsync(filter, + input.Sorting, input.MaxResultCount, input.SkipCount); + + return new PagedResultDto(totalCount, + ObjectMapper.Map, List>(sendRecords)); + } + + [Authorize(WebhooksManagementPermissions.WebhooksSendAttempts.Resend)] + public async virtual Task ResendAsync(Guid id) + { + var sendRecord = await RecordRepository.GetAsync(id); + var sendEvent = await EventRepository.GetAsync(sendRecord.WebhookEventId); + var subscription = await SubscriptionRepository.GetAsync(sendRecord.WebhookSubscriptionId); + + await BackgroundJobManager.EnqueueAsync(new WebhookSenderArgs + { + TenantId = CurrentTenant.Id, + WebhookSubscriptionId = sendRecord.WebhookSubscriptionId, + WebhookEventId = sendRecord.WebhookEventId, + WebhookName = sendEvent.WebhookName, + WebhookUri = subscription.WebhookUri, + Data = sendEvent.Data, + Headers = subscription.GetWebhookHeaders(), + Secret = subscription.Secret, + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionAppService.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionAppService.cs new file mode 100644 index 000000000..3b140c91b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionAppService.cs @@ -0,0 +1,150 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.WebhooksManagement.Authorization; +using LINGYUN.Abp.WebhooksManagement.Extensions; +using Microsoft.AspNetCore.Authorization; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +[Authorize(WebhooksManagementPermissions.WebhookSubscription.Default)] +public class WebhookSubscriptionAppService : WebhooksManagementAppServiceBase, IWebhookSubscriptionAppService +{ + protected IWebhookDefinitionManager WebhookDefinitionManager { get; } + protected IWebhookSubscriptionRepository SubscriptionRepository { get; } + + public WebhookSubscriptionAppService( + IWebhookDefinitionManager webhookDefinitionManager, + IWebhookSubscriptionRepository subscriptionRepository) + { + WebhookDefinitionManager = webhookDefinitionManager; + SubscriptionRepository = subscriptionRepository; + } + + [Authorize(WebhooksManagementPermissions.WebhookSubscription.Create)] + public async virtual Task CreateAsync(WebhookSubscriptionCreateInput input) + { + await CheckSubscribedAsync(input); + + var subscription = new WebhookSubscription( + GuidGenerator.Create(), + input.WebhookUri, + input.Secret, + JsonConvert.SerializeObject(input.Webhooks), + JsonConvert.SerializeObject(input.Headers), + CurrentTenant.Id); + + await SubscriptionRepository.InsertAsync(subscription); + + await CurrentUnitOfWork.SaveChangesAsync(); + + return subscription.ToWebhookSubscriptionDto(); + } + + [Authorize(WebhooksManagementPermissions.WebhookSubscription.Delete)] + public virtual Task DeleteAsync(Guid id) + { + return SubscriptionRepository.DeleteAsync(id); + } + + public async virtual Task GetAsync(Guid id) + { + var subscription = await SubscriptionRepository.GetAsync(id); + + return subscription.ToWebhookSubscriptionDto(); + } + + public async virtual Task> GetListAsync(WebhookSubscriptionGetListInput input) + { + var filter = new WebhookSubscriptionFilter + { + Filter = input.Filter, + BeginCreationTime = input.BeginCreationTime, + EndCreationTime = input.EndCreationTime, + IsActive = input.IsActive, + Secret = input.Secret, + TenantId = input.TenantId, + Webhooks = input.Webhooks, + WebhookUri = input.WebhookUri + }; + + var totalCount = await SubscriptionRepository.GetCountAsync(filter); + var subscriptions = await SubscriptionRepository.GetListAsync(filter, + input.Sorting, input.MaxResultCount, input.SkipCount); + + return new PagedResultDto(totalCount, + subscriptions.Select(subscription => subscription.ToWebhookSubscriptionDto()).ToList()); + } + + [Authorize(WebhooksManagementPermissions.WebhookSubscription.Update)] + public async virtual Task UpdateAsync(Guid id, WebhookSubscriptionUpdateInput input) + { + var subscription = await SubscriptionRepository.GetAsync(id); + if (!string.Equals(subscription.WebhookUri, input.WebhookUri)) + { + await CheckSubscribedAsync(input); + } + + subscription.SetSecret(input.Secret); + subscription.SetWebhookUri(input.WebhookUri); + subscription.SetWebhooks(input.ToSubscribedWebhooksString()); + subscription.SetHeaders(input.ToWebhookHeadersString()); + subscription.IsActive = input.IsActive; + + await SubscriptionRepository.UpdateAsync(subscription); + + await CurrentUnitOfWork.SaveChangesAsync(); + + return subscription.ToWebhookSubscriptionDto(); + } + + public async virtual Task> GetAllAvailableWebhooksAsync() + { + var groups = WebhookDefinitionManager.GetGroups(); + var definitions = new List(); + + foreach (var groupDefinition in groups) + { + var group = new WebhookAvailableGroupDto + { + Name = groupDefinition.Name, + DisplayName = groupDefinition.DisplayName?.Localize(StringLocalizerFactory), + }; + + foreach (var webhookDefinition in groupDefinition.Webhooks.OrderBy(d => d.Name)) + { + if (await WebhookDefinitionManager.IsAvailableAsync(CurrentTenant.Id, webhookDefinition.Name)) + { + group.Webhooks.Add(new WebhookAvailableDto + { + Name = webhookDefinition.Name, + Description = webhookDefinition.Description?.Localize(StringLocalizerFactory), + DisplayName = webhookDefinition.DisplayName?.Localize(StringLocalizerFactory) + }); + } + } + + definitions.Add(group); + } + + return new ListResultDto(definitions.OrderBy(d => d.Name).ToList()); + } + + protected async virtual Task CheckSubscribedAsync(WebhookSubscriptionCreateOrUpdateInput input) + { + foreach (var webhookName in input.Webhooks) + { + if (await SubscriptionRepository.IsSubscribedAsync(CurrentTenant.Id, input.WebhookUri, webhookName)) + { + throw new BusinessException(WebhooksManagementErrorCodes.WebhookSubscription.DuplicateSubscribed) + .WithData(nameof(WebhookSubscription.WebhookUri), input.WebhookUri) + .WithData(nameof(WebhookSubscription.Webhooks), webhookName); + } + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementAppServiceBase.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementAppServiceBase.cs new file mode 100644 index 000000000..3953ab3e0 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementAppServiceBase.cs @@ -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); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationMapperProfile.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationMapperProfile.cs new file mode 100644 index 000000000..11ab195a0 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationMapperProfile.cs @@ -0,0 +1,12 @@ +using AutoMapper; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhooksManagementApplicationMapperProfile : Profile +{ + public WebhooksManagementApplicationMapperProfile() + { + CreateMap(); + CreateMap(); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationModule.cs new file mode 100644 index 000000000..eef27929b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Application/LINGYUN/Abp/WebhooksManagement/WebhooksManagementApplicationModule.cs @@ -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(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/LINGYUN.Abp.WebhooksManagement.Dapr.Client.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/LINGYUN.Abp.WebhooksManagement.Dapr.Client.csproj new file mode 100644 index 000000000..e07d4c79d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/LINGYUN.Abp.WebhooksManagement.Dapr.Client.csproj @@ -0,0 +1,16 @@ + + + + + + + net6.0 + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDaprClientModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDaprClientModule.cs new file mode 100644 index 000000000..bb89de573 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Dapr.Client/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDaprClientModule.cs @@ -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); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN.Abp.WebhooksManagement.Domain.Shared.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN.Abp.WebhooksManagement.Domain.Shared.csproj new file mode 100644 index 000000000..682b8eff5 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN.Abp.WebhooksManagement.Domain.Shared.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/Resources/en.json b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/Resources/en.json new file mode 100644 index 000000000..a64f32f2a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/Resources/en.json @@ -0,0 +1,19 @@ +{ + "culture": "en", + "texts": { + "Features:WebhooksManagement": "Webhooks", + "Permission:WebhooksManagement": "Webhooks", + "Permission:Subscriptions": "Subscriptions", + "Permission:SendAttempts": "Attempts", + "Permission:Create": "Create", + "Permission:Update": "Update", + "Permission:Delete": "Delete", + "Permission:Resend": "Resend", + "Permission:Publish": "Publish", + "Permission:ManageSettings": "Manage Settings", + "Webhooks:010001": "Payload address {WebhookUri} has been mounted event {Webhooks}!", + "Webhooks:Tests": "Tests", + "Webhooks:CheckConnect": "Check Connect", + "Webhooks:CheckConnectDesc": "When a third-party service is connected, it is used to check whether the communication is normal." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/Resources/zh-Hans.json b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..efb4975cd --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/Resources/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "culture": "zh-Hans", + "texts": { + "Features:WebhooksManagement": "Webhooks", + "Permission:WebhooksManagement": "Webhooks", + "Permission:Subscriptions": "管理订阅", + "Permission:SendAttempts": "管理发送", + "Permission:Create": "创建", + "Permission:Update": "编辑", + "Permission:Delete": "删除", + "Permission:Resend": "重新发送", + "Permission:Publish": "发布事件", + "Permission:ManageSettings": "管理设置", + "Webhooks:010001": "载荷地址 {WebhookUri} 已经挂载事件 {Webhooks}!", + "Webhooks:Tests": "测试", + "Webhooks:CheckConnect": "检查连接", + "Webhooks:CheckConnectDesc": "第三方服务接入时,用于检查是否通讯正常." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/WebhooksManagementResource.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/WebhooksManagementResource.cs new file mode 100644 index 000000000..c95515cad --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/Localization/WebhooksManagementResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.WebhooksManagement.Localization; + +[LocalizationResourceName("WebhooksManagement")] +public class WebhooksManagementResource +{ +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConfiguration.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConfiguration.cs new file mode 100644 index 000000000..bf7e418ce --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConfiguration.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.ObjectExtending.Modularity; + +namespace LINGYUN.Abp.WebhooksManagement.ObjectExtending; + +public class WebhooksManagementModuleExtensionConfiguration : ModuleExtensionConfiguration +{ + public WebhooksManagementModuleExtensionConfiguration ConfigureWebhooksManagement( + Action configureAction) + { + return this + .ConfigureEntity(WebhooksManagementModuleExtensionConsts.EntityNames.WebhookEvent, configureAction) + .ConfigureEntity(WebhooksManagementModuleExtensionConsts.EntityNames.WebhookSendAttempt, configureAction) + .ConfigureEntity(WebhooksManagementModuleExtensionConsts.EntityNames.WebhookSubscription, configureAction); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConfigurationDictionaryExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConfigurationDictionaryExtensions.cs new file mode 100644 index 000000000..fe0bff94b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConfigurationDictionaryExtensions.cs @@ -0,0 +1,17 @@ +using System; +using Volo.Abp.ObjectExtending.Modularity; + +namespace LINGYUN.Abp.WebhooksManagement.ObjectExtending; + +public static class WebhooksManagementModuleExtensionConfigurationDictionaryExtensions +{ + public static ModuleExtensionConfigurationDictionary ConfigureWebhooksManagement( + this ModuleExtensionConfigurationDictionary modules, + Action configureAction) + { + return modules.ConfigureModule( + WebhooksManagementModuleExtensionConsts.ModuleName, + configureAction + ); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConsts.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConsts.cs new file mode 100644 index 000000000..a51d51035 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/ObjectExtending/WebhooksManagementModuleExtensionConsts.cs @@ -0,0 +1,13 @@ +namespace LINGYUN.Abp.WebhooksManagement.ObjectExtending; + +public static class WebhooksManagementModuleExtensionConsts +{ + public const string ModuleName = "WebhooksManagement"; + + public static class EntityNames + { + public const string WebhookEvent = "WebhookEvent"; + public const string WebhookSendAttempt = "WebhookSendAttempt"; + public const string WebhookSubscription = "WebhookSubscription"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookEventEto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookEventEto.cs new file mode 100644 index 000000000..f6625a851 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookEventEto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.WebhooksManagement; + +[Serializable] +public class WebhookEventEto : IMultiTenant +{ + public Guid Id { get; set; } + public Guid? TenantId { get; set; } + public string WebhookName { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookEventRecordConsts.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookEventRecordConsts.cs new file mode 100644 index 000000000..31f4cf431 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookEventRecordConsts.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public static class WebhookEventRecordConsts +{ + public static int MaxWebhookNameLength { get; set; } = 100; + public static int MaxDataLength { get; set; } = int.MaxValue; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSendAttemptEto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSendAttemptEto.cs new file mode 100644 index 000000000..12f1cb812 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSendAttemptEto.cs @@ -0,0 +1,16 @@ +using System; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.WebhooksManagement; + +[Serializable] +public class WebhookSendAttemptEto : IMultiTenant +{ + public Guid Id { get; set; } + + public Guid? TenantId { get; set; } + + public Guid WebhookEventId { get; set; } + + public Guid WebhookSubscriptionId { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordConsts.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordConsts.cs new file mode 100644 index 000000000..464658f04 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordConsts.cs @@ -0,0 +1,6 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public static class WebhookSendRecordConsts +{ + public static int MaxResponseLength { get; set; } = int.MaxValue; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionConsts.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionConsts.cs new file mode 100644 index 000000000..70a51fc17 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionConsts.cs @@ -0,0 +1,9 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public static class WebhookSubscriptionConsts +{ + public static int MaxWebhookUriLength { get; set; } = 255; + public static int MaxSecretLength { get; set; } = 128; + public static int MaxWebhooksLength { get; set; } = int.MaxValue; + public static int MaxHeadersLength { get; set; } = int.MaxValue; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionEto.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionEto.cs new file mode 100644 index 000000000..157d6bc04 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionEto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.WebhooksManagement; + +[Serializable] +public class WebhookSubscriptionEto : IMultiTenant +{ + public Guid Id { get; set; } + + public Guid? TenantId { get; set; } + + public string WebhookUri { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainSharedModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainSharedModule.cs new file mode 100644 index 000000000..1e9234e7a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainSharedModule.cs @@ -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(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add() + .AddVirtualJson("/LINGYUN/Abp/WebhooksManagement/Localization/Resources"); + }); + + Configure(options => + { + options.MapCodeNamespace(WebhooksManagementErrorCodes.Namespace, typeof(WebhooksManagementResource)); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhooksManagementErrorCodes.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhooksManagementErrorCodes.cs new file mode 100644 index 000000000..5c13db31c --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain.Shared/LINGYUN/Abp/WebhooksManagement/WebhooksManagementErrorCodes.cs @@ -0,0 +1,13 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public static class WebhooksManagementErrorCodes +{ + public const string Namespace = "Webhooks"; + + public static class WebhookSubscription + { + public const string Prefix = Namespace + ":010"; + + public const string DuplicateSubscribed = Prefix + "001"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN.Abp.WebhooksManagement.Domain.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN.Abp.WebhooksManagement.Domain.csproj new file mode 100644 index 000000000..5234b20c6 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN.Abp.WebhooksManagement.Domain.csproj @@ -0,0 +1,22 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/DefaultWebhookManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/DefaultWebhookManager.cs new file mode 100644 index 000000000..c0ab03de1 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/DefaultWebhookManager.cs @@ -0,0 +1,60 @@ +using LINGYUN.Abp.Webhooks; +using System; +using System.Net; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +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, + IWebhookSendAttemptStore webhookSendAttemptStore, + IUnitOfWorkManager unitOfWorkManager, + IWebhookSendRecordRepository webhookSendAttemptRepository) + : base(webhookSendAttemptStore) + { + CurrentTenant = currentTenant; + GuidGenerator = guidGenerator; + UnitOfWorkManager = unitOfWorkManager; + WebhookSendAttemptRepository = webhookSendAttemptRepository; + } + + [UnitOfWork] + public async override Task InsertAndGetIdWebhookSendAttemptAsync(WebhookSenderArgs webhookSenderArgs) + { + using (CurrentTenant.Change(webhookSenderArgs.TenantId)) + { + var record = new WebhookSendRecord( + GuidGenerator.Create(), + webhookSenderArgs.WebhookEventId, + webhookSenderArgs.WebhookSubscriptionId, + webhookSenderArgs.TenantId); + + await WebhookSendAttemptRepository.InsertAsync(record); + + return record.Id; + } + } + + [UnitOfWork] + public async override Task StoreResponseOnWebhookSendAttemptAsync(Guid webhookSendAttemptId, Guid? tenantId, HttpStatusCode? statusCode, string content) + { + using (CurrentTenant.Change(tenantId)) + { + var record = await WebhookSendAttemptRepository.GetAsync(webhookSendAttemptId); + record.SetResponse(content, statusCode); + + await WebhookSendAttemptRepository.UpdateAsync(record); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionExtensions.cs new file mode 100644 index 000000000..3630b3760 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionExtensions.cs @@ -0,0 +1,29 @@ +using LINGYUN.Abp.Webhooks; +using Newtonsoft.Json; +using System.Linq; + +namespace LINGYUN.Abp.WebhooksManagement.Extensions +{ + public static class WebhookSubscriptionExtensions + { + public static string ToSubscribedWebhooksString(this WebhookSubscriptionInfo webhookSubscription) + { + if (webhookSubscription.Webhooks.Any()) + { + return JsonConvert.SerializeObject(webhookSubscription.Webhooks); + } + + return null; + } + + public static string ToWebhookHeadersString(this WebhookSubscriptionInfo webhookSubscription) + { + if (webhookSubscription.Headers.Any()) + { + return JsonConvert.SerializeObject(webhookSubscription.Headers); + } + + return null; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionInfoExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionInfoExtensions.cs new file mode 100644 index 000000000..8068dfa10 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Extensions/WebhookSubscriptionInfoExtensions.cs @@ -0,0 +1,175 @@ +using LINGYUN.Abp.Webhooks; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.WebhooksManagement.Extensions +{ + public static class WebhookSubscriptionInfoExtensions + { + /// + /// Return List of subscribed webhooks definitions + /// + /// + public static List GetSubscribedWebhooks(this WebhookSubscription webhookSubscription) + { + if (webhookSubscription.Webhooks.IsNullOrWhiteSpace()) + { + return new List(); + } + + return JsonConvert.DeserializeObject>(webhookSubscription.Webhooks); + } + + /// + /// Adds webhook subscription to if not exists + /// + /// + /// webhook unique name + public static void SubscribeWebhook(this WebhookSubscription webhookSubscription, string name) + { + name = name.Trim(); + if (name.IsNullOrWhiteSpace()) + { + throw new ArgumentNullException(nameof(name), $"{nameof(name)} can not be null, empty or whitespace!"); + } + + var webhookDefinitions = webhookSubscription.GetSubscribedWebhooks(); + if (webhookDefinitions.Contains(name)) + { + return; + } + + webhookDefinitions.Add(name); + webhookSubscription.SetWebhooks(JsonConvert.SerializeObject(webhookDefinitions)); + } + + /// + /// Removes webhook subscription from if exists + /// + /// + /// webhook unique name + public static void UnsubscribeWebhook(this WebhookSubscription webhookSubscription, string name) + { + name = name.Trim(); + if (name.IsNullOrWhiteSpace()) + { + throw new ArgumentNullException(nameof(name), $"{nameof(name)} can not be null, empty or whitespace!"); + } + + var webhookDefinitions = webhookSubscription.GetSubscribedWebhooks(); + if (!webhookDefinitions.Contains(name)) + { + return; + } + + webhookDefinitions.Remove(name); + webhookSubscription.SetWebhooks(JsonConvert.SerializeObject(webhookDefinitions)); + } + + /// + /// Clears all + /// + /// + public static void RemoveAllSubscribedWebhooks(this WebhookSubscription webhookSubscription) + { + webhookSubscription.SetWebhooks(null); + } + + /// + /// if subscribed to given webhook + /// + /// + public static bool IsSubscribed(this WebhookSubscription webhookSubscription, string webhookName) + { + if (webhookSubscription.Webhooks.IsNullOrWhiteSpace()) + { + return false; + } + + return webhookSubscription.GetSubscribedWebhooks().Contains(webhookName); + } + + /// + /// Returns additional webhook headers + /// + /// + public static IDictionary GetWebhookHeaders(this WebhookSubscription webhookSubscription) + { + if (webhookSubscription.Headers.IsNullOrWhiteSpace()) + { + return new Dictionary(); + } + + return JsonConvert.DeserializeObject>(webhookSubscription.Headers); + } + + /// + /// Adds webhook subscription to if not exists + /// + public static void AddWebhookHeader(this WebhookSubscription webhookSubscription, string key, string value) + { + if (key.IsNullOrWhiteSpace() ) + { + throw new ArgumentNullException(nameof(key), $"{nameof(key)} can not be null, empty or whitespace!"); + } + + if (value.IsNullOrWhiteSpace()) + { + throw new ArgumentNullException(nameof(value), $"{nameof(value)} can not be null, empty or whitespace!"); + } + + var headers = webhookSubscription.GetWebhookHeaders(); + headers[key] = value; + + webhookSubscription.SetHeaders(JsonConvert.SerializeObject(headers)); + } + + /// + /// Adds webhook subscription to if not exists + /// + /// + /// Key of header + public static void RemoveWebhookHeader(this WebhookSubscription webhookSubscription, string header) + { + if (header.IsNullOrWhiteSpace()) + { + throw new ArgumentNullException(nameof(header), $"{nameof(header)} can not be null, empty or whitespace!"); + } + + var headers = webhookSubscription.GetWebhookHeaders(); + + if (!headers.ContainsKey(header)) + { + return; + } + + headers.Remove(header); + + webhookSubscription.SetHeaders(JsonConvert.SerializeObject(headers)); + } + + /// + /// Clears all + /// + /// + public static void RemoveAllWebhookHeaders(this WebhookSubscription webhookSubscription) + { + webhookSubscription.SetHeaders(null); + } + + public static WebhookSubscriptionInfo ToWebhookSubscriptionInfo(this WebhookSubscription webhookSubscription) + { + return new WebhookSubscriptionInfo + { + Id = webhookSubscription.Id, + TenantId = webhookSubscription.TenantId, + IsActive = webhookSubscription.IsActive, + Secret = webhookSubscription.Secret, + WebhookUri = webhookSubscription.WebhookUri, + Webhooks = webhookSubscription.GetSubscribedWebhooks(), + Headers = webhookSubscription.GetWebhookHeaders() + }; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookEventRecordRepository.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookEventRecordRepository.cs new file mode 100644 index 000000000..45a69d4f2 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookEventRecordRepository.cs @@ -0,0 +1,8 @@ +using System; +using Volo.Abp.Domain.Repositories; + +namespace LINGYUN.Abp.WebhooksManagement; + +public interface IWebhookEventRecordRepository : IRepository +{ +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookSendRecordRepository.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookSendRecordRepository.cs new file mode 100644 index 000000000..a94eb35ee --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookSendRecordRepository.cs @@ -0,0 +1,22 @@ +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 +{ + Task GetCountAsync( + WebhookSendRecordFilter filter, + CancellationToken cancellationToken = default); + + Task> GetListAsync( + WebhookSendRecordFilter filter, + string sorting = nameof(WebhookSendRecord.CreationTime), + int maxResultCount = 10, + int skipCount = 10, + bool includeDetails = false, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookSubscriptionRepository.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookSubscriptionRepository.cs new file mode 100644 index 000000000..069e8375c --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/IWebhookSubscriptionRepository.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace LINGYUN.Abp.WebhooksManagement; + +public interface IWebhookSubscriptionRepository : IRepository +{ + Task IsSubscribedAsync( + Guid? tenantId, + string webhookUri, + string webhookName, + CancellationToken cancellationToken = default); + + Task GetCountAsync( + WebhookSubscriptionFilter filter, + CancellationToken cancellationToken = default); + + Task> GetListAsync( + WebhookSubscriptionFilter filter, + string sorting = $"{nameof(WebhookSubscription.CreationTime)} DESC", + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Settings/WebhooksManagementSettingDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Settings/WebhooksManagementSettingDefinitionProvider.cs new file mode 100644 index 000000000..e3aa21264 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Settings/WebhooksManagementSettingDefinitionProvider.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.WebhooksManagement.Settings +{ + public class WebhooksManagementSettingDefinitionProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Settings/WebhooksManagementSettings.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Settings/WebhooksManagementSettings.cs new file mode 100644 index 000000000..1de9fcda9 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Settings/WebhooksManagementSettings.cs @@ -0,0 +1,7 @@ +namespace LINGYUN.Abp.WebhooksManagement.Settings +{ + public static class WebhooksManagementSettings + { + public const string GroupName = "WebhooksManagement"; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookEventRecord.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookEventRecord.cs new file mode 100644 index 000000000..82fc7fb50 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookEventRecord.cs @@ -0,0 +1,31 @@ +using System; +using Volo.Abp; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookEventRecord : Entity, 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 = Check.NotNullOrWhiteSpace(webhookName, nameof(webhookName), WebhookEventRecordConsts.MaxWebhookNameLength); + Data = Check.Length(data, nameof(data), WebhookEventRecordConsts.MaxDataLength); + TenantId = tenantId; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookEventStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookEventStore.cs new file mode 100644 index 000000000..ff14e571a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookEventStore.cs @@ -0,0 +1,52 @@ +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 ObjectMapper => LazyServiceProvider.LazyGetRequiredService>(); + + protected IWebhookEventRecordRepository WebhookEventRepository { get; } + + public WebhookEventStore( + IWebhookEventRecordRepository webhookEventRepository) + { + WebhookEventRepository = webhookEventRepository; + } + + [UnitOfWork] + public async virtual Task GetAsync(Guid? tenantId, Guid id) + { + using (CurrentTenant.Change(tenantId)) + { + var record = await WebhookEventRepository.GetAsync(id); + + return ObjectMapper.Map(record); + } + } + + [UnitOfWork] + public async virtual Task InsertAndGetIdAsync(WebhookEvent webhookEvent) + { + using (CurrentTenant.Change(webhookEvent.TenantId)) + { + var record = new WebhookEventRecord( + GuidGenerator.Create(), + webhookEvent.WebhookName, + webhookEvent.Data, + webhookEvent.TenantId) + { + CreationTime = Clock.Now, + }; + + await WebhookEventRepository.InsertAsync(record); + + return record.Id; + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendAttemptStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendAttemptStore.cs new file mode 100644 index 000000000..1b469c105 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendAttemptStore.cs @@ -0,0 +1,122 @@ +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.ObjectMapping; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSendAttemptStore : DomainService, IWebhookSendAttemptStore +{ + protected IObjectMapper ObjectMapper => LazyServiceProvider.LazyGetRequiredService>(); + + protected IWebhookSendRecordRepository WebhookSendAttemptRepository { get; } + + public WebhookSendAttemptStore( + IWebhookSendRecordRepository webhookSendAttemptRepository) + { + WebhookSendAttemptRepository = webhookSendAttemptRepository; + } + + [UnitOfWork] + public async virtual Task<(int TotalCount, IReadOnlyCollection Webhooks)> GetAllSendAttemptsBySubscriptionAsPagedListAsync( + Guid? tenantId, + Guid subscriptionId, + int maxResultCount, + int skipCount) + { + 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>(list); + + return ValueTuple.Create(totalCount, webHooks.ToImmutableList()); + } + } + + [UnitOfWork] + public async virtual Task> GetAllSendAttemptsByWebhookEventIdAsync( + Guid? tenantId, + Guid webhookEventId) + { + using (CurrentTenant.Change(tenantId)) + { + var queryable = await WebhookSendAttemptRepository.GetQueryableAsync(); + + var list = await AsyncExecuter.ToListAsync(queryable + .Where(attempt => attempt.WebhookEventId == webhookEventId) + .OrderByDescending(attempt => attempt.CreationTime) + ); + + return ObjectMapper.Map, List>(list); + } + } + + [UnitOfWork] + public async virtual Task GetAsync( + Guid? tenantId, + Guid id) + { + using (CurrentTenant.Change(tenantId)) + { + var sendAttempt = await WebhookSendAttemptRepository.GetAsync(id); + + return ObjectMapper.Map(sendAttempt); + } + } + + [UnitOfWork] + public async virtual Task GetSendAttemptCountAsync( + Guid? tenantId, + Guid webhookEventId, + Guid webhookSubscriptionId) + { + using (CurrentTenant.Change(tenantId)) + { + return await WebhookSendAttemptRepository.CountAsync(attempt => + attempt.WebhookEventId == webhookEventId && + attempt.WebhookSubscriptionId == webhookSubscriptionId); + } + } + + [UnitOfWork] + public async virtual Task HasXConsecutiveFailAsync( + Guid? tenantId, + Guid subscriptionId, + int failCount) + { + using (CurrentTenant.Change(tenantId)) + { + if (await WebhookSendAttemptRepository.CountAsync(x => x.WebhookSubscriptionId == subscriptionId) < failCount) + { + return false; + } + else + { + var queryable = await WebhookSendAttemptRepository.GetQueryableAsync(); + + return !await AsyncExecuter.AnyAsync(queryable + .OrderByDescending(attempt => attempt.CreationTime) + .Take(failCount) + .Where(attempt => attempt.ResponseStatusCode == HttpStatusCode.OK) + ); + } + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendRecord.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendRecord.cs new file mode 100644 index 000000000..0d0d0115a --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendRecord.cs @@ -0,0 +1,51 @@ +using System; +using System.Net; +using Volo.Abp; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSendRecord : Entity, 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 = Check.Length(response, nameof(response), WebhookSendRecordConsts.MaxResponseLength); + ResponseStatusCode = statusCode; + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordFilter.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordFilter.cs new file mode 100644 index 000000000..44f034c30 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordFilter.cs @@ -0,0 +1,19 @@ +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 HttpStatusCode? ResponseStatusCode { get; set; } + + public DateTime? BeginCreationTime { get; set; } + + public DateTime? EndCreationTime { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscription.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscription.cs new file mode 100644 index 000000000..50476fce4 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscription.cs @@ -0,0 +1,54 @@ +using System; +using Volo.Abp; +using Volo.Abp.Domain.Entities.Auditing; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSubscription : CreationAuditedEntity +{ + 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) + { + SetSecret(secret); + SetWebhookUri(webhookUri); + SetWebhooks(webhooks); + SetHeaders(headers); + TenantId = tenantId; + + IsActive = true; + } + + public void SetSecret(string secret) + { + Secret = Check.NotNullOrWhiteSpace(secret, nameof(secret), WebhookSubscriptionConsts.MaxSecretLength); + } + + public void SetWebhookUri(string webhookUri) + { + WebhookUri = Check.NotNullOrWhiteSpace(webhookUri, nameof(webhookUri), WebhookSubscriptionConsts.MaxWebhookUriLength); + } + + public void SetWebhooks(string webhooks) + { + Webhooks = Check.Length(webhooks, nameof(webhooks), WebhookSubscriptionConsts.MaxWebhooksLength); + } + + public void SetHeaders(string headers) + { + Headers = Check.Length(headers, nameof(headers), WebhookSubscriptionConsts.MaxHeadersLength); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionFilter.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionFilter.cs new file mode 100644 index 000000000..adbd92c7d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionFilter.cs @@ -0,0 +1,22 @@ +using System; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSubscriptionFilter +{ + public string Filter { get; set; } + + public Guid? TenantId { get; set; } + + public string WebhookUri { get; set; } + + public string Secret { get; set; } + + public bool? IsActive { get; set; } + + public string Webhooks { get; set; } + + public DateTime? BeginCreationTime { get; set; } + + public DateTime? EndCreationTime { get; set; } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionsStore.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionsStore.cs new file mode 100644 index 000000000..fb14c7bae --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionsStore.cs @@ -0,0 +1,153 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.WebhooksManagement.Extensions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhookSubscriptionsStore : DomainService, IWebhookSubscriptionsStore +{ + protected IWebhookSubscriptionRepository SubscriptionRepository { get; } + + public WebhookSubscriptionsStore( + IWebhookSubscriptionRepository subscriptionRepository) + { + SubscriptionRepository = subscriptionRepository; + } + + [UnitOfWork] + public async virtual Task DeleteAsync(Guid id) + { + using (CurrentTenant.Change(null)) + { + await SubscriptionRepository.DeleteAsync(id); + } + } + + [UnitOfWork] + public async virtual Task> GetAllSubscriptionsAsync(Guid? tenantId) + { + using (CurrentTenant.Change(null)) + { + var queryable = await SubscriptionRepository.GetQueryableAsync(); + + queryable = queryable.Where(x => x.TenantId == tenantId); + + var subscriptions = await AsyncExecuter.ToListAsync(queryable); + + return subscriptions.Select(subscription => subscription.ToWebhookSubscriptionInfo()).ToList(); + } + } + + [UnitOfWork] + public async virtual Task> GetAllSubscriptionsAsync(Guid? tenantId, string webhookName) + { + using (CurrentTenant.Change(null)) + { + var queryable = await SubscriptionRepository.GetQueryableAsync(); + + queryable = queryable.Where(x => + x.TenantId == tenantId && + x.IsActive && + x.Webhooks.Contains("\"" + webhookName + "\"")); + + var subscriptions = await AsyncExecuter.ToListAsync(queryable); + + return subscriptions.Select(subscription => subscription.ToWebhookSubscriptionInfo()).ToList(); + } + } + + [UnitOfWork] + public async virtual Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds) + { + using (CurrentTenant.Change(null)) + { + var queryable = await SubscriptionRepository.GetQueryableAsync(); + + var subscriptions = await AsyncExecuter.ToListAsync(queryable.Where(x => tenantIds.Contains(x.TenantId))); + + return subscriptions.Select(subscription => subscription.ToWebhookSubscriptionInfo()).ToList(); + } + } + + [UnitOfWork] + public async virtual Task> GetAllSubscriptionsOfTenantsAsync(Guid?[] tenantIds, string webhookName) + { + using (CurrentTenant.Change(null)) + { + var queryable = await SubscriptionRepository.GetQueryableAsync(); + + queryable = queryable.Where(x => + x.IsActive && + tenantIds.Contains(x.TenantId) && + x.Webhooks.Contains("\"" + webhookName + "\"")); + + var subscriptions = await AsyncExecuter.ToListAsync(queryable); + + return subscriptions.Select(subscription => subscription.ToWebhookSubscriptionInfo()).ToList(); + } + } + + [UnitOfWork] + public async virtual Task GetAsync(Guid id) + { + using (CurrentTenant.Change(null)) + { + var subscription = await SubscriptionRepository.GetAsync(id); + + return subscription.ToWebhookSubscriptionInfo(); + } + } + + [UnitOfWork] + public async virtual Task InsertAsync(WebhookSubscriptionInfo webhookSubscription) + { + using (CurrentTenant.Change(null)) + { + var subscription = new WebhookSubscription( + webhookSubscription.Id, + webhookSubscription.WebhookUri, + webhookSubscription.Secret, + JsonConvert.SerializeObject(webhookSubscription.Webhooks), + JsonConvert.SerializeObject(webhookSubscription.Headers), + webhookSubscription.TenantId); + + await SubscriptionRepository.InsertAsync(subscription); + } + } + + [UnitOfWork] + public async virtual Task IsSubscribedAsync(Guid? tenantId, string webhookName) + { + using (CurrentTenant.Change(null)) + { + var queryable = await SubscriptionRepository.GetQueryableAsync(); + + queryable = queryable.Where(x => + x.TenantId == tenantId && + x.IsActive && + x.Webhooks.Contains("\"" + webhookName + "\"")); + + return await AsyncExecuter.AnyAsync(queryable); + } + } + + [UnitOfWork] + public async virtual Task UpdateAsync(WebhookSubscriptionInfo webhookSubscription) + { + using (CurrentTenant.Change(webhookSubscription.TenantId)) + { + var subscription = await SubscriptionRepository.GetAsync(webhookSubscription.Id); + subscription.SetWebhookUri(webhookSubscription.WebhookUri); + subscription.SetWebhooks(webhookSubscription.ToSubscribedWebhooksString()); + subscription.SetHeaders(webhookSubscription.ToWebhookHeadersString()); + + await SubscriptionRepository.UpdateAsync(subscription); + } + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Webhooks/WebhooksDefinitionProvider.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Webhooks/WebhooksDefinitionProvider.cs new file mode 100644 index 000000000..0389c1573 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Webhooks/WebhooksDefinitionProvider.cs @@ -0,0 +1,26 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.WebhooksManagement.Localization; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.WebhooksManagement.Webhooks; + +public class WebhooksDefinitionProvider : WebhookDefinitionProvider +{ + public override void Define(IWebhookDefinitionContext context) + { + var testsGroup = context.AddGroup( + WebhooksNames.GroupName, + L("Webhooks:Tests")); + + testsGroup.AddWebhooks( + new WebhookDefinition( + WebhooksNames.CheckConnect, + L("Webhooks:CheckConnect"), + L("Webhooks:CheckConnectDesc"))); + } + + private static ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Webhooks/WebhooksNames.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Webhooks/WebhooksNames.cs new file mode 100644 index 000000000..f650eec4b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/Webhooks/WebhooksNames.cs @@ -0,0 +1,8 @@ +namespace LINGYUN.Abp.WebhooksManagement.Webhooks; + +public static class WebhooksNames +{ + public const string GroupName = "abp.webhooks.tests"; + + public const string CheckConnect = GroupName + ".check_connect"; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDbProperties.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDbProperties.cs new file mode 100644 index 000000000..33ddda08e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDbProperties.cs @@ -0,0 +1,11 @@ +namespace LINGYUN.Abp.WebhooksManagement; + +public static class WebhooksManagementDbProperties +{ + public static string DbTablePrefix { get; set; } = "AbpWebhooks"; + + public static string DbSchema { get; set; } = null; + + + public const string ConnectionStringName = "WebhooksManagement"; +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainMapperProfile.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainMapperProfile.cs new file mode 100644 index 000000000..231739e73 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainMapperProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using LINGYUN.Abp.Webhooks; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.WebhooksManagement; + +public class WebhooksManagementDomainMapperProfile : Profile +{ + public WebhooksManagementDomainMapperProfile() + { + CreateMap(); + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainModule.cs new file mode 100644 index 000000000..18903b4b3 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.Domain/LINGYUN/Abp/WebhooksManagement/WebhooksManagementDomainModule.cs @@ -0,0 +1,62 @@ +using LINGYUN.Abp.Webhooks; +using LINGYUN.Abp.WebhooksManagement.ObjectExtending; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AutoMapper; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending.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(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + + Configure(options => + { + options.EtoMappings.Add(); + options.EtoMappings.Add(); + options.EtoMappings.Add(); + + options.AutoEventSelectors.Add(); + options.AutoEventSelectors.Add(); + options.AutoEventSelectors.Add(); + }); + } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + // 扩展实体配置 + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + WebhooksManagementModuleExtensionConsts.ModuleName, + WebhooksManagementModuleExtensionConsts.EntityNames.WebhookEvent, + typeof(WebhookEventRecord) + ); + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + WebhooksManagementModuleExtensionConsts.ModuleName, + WebhooksManagementModuleExtensionConsts.EntityNames.WebhookSubscription, + typeof(WebhookSubscription) + ); + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( + WebhooksManagementModuleExtensionConsts.ModuleName, + WebhooksManagementModuleExtensionConsts.EntityNames.WebhookSendAttempt, + typeof(WebhookSendRecord) + ); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore.csproj new file mode 100644 index 000000000..6eb433ff2 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore.csproj @@ -0,0 +1,19 @@ + + + + + + + net6.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookEventRecordRepository.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookEventRecordRepository.cs new file mode 100644 index 000000000..606b69aff --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookEventRecordRepository.cs @@ -0,0 +1,15 @@ +using System; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; + +public class EfCoreWebhookEventRecordRepository : + EfCoreRepository, + IWebhookEventRecordRepository +{ + public EfCoreWebhookEventRecordRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookSendRecordRepository.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookSendRecordRepository.cs new file mode 100644 index 000000000..9f1270348 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookSendRecordRepository.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; + +public class EfCoreWebhookSendRecordRepository : + EfCoreRepository, + IWebhookSendRecordRepository +{ + public EfCoreWebhookSendRecordRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async virtual Task GetCountAsync( + WebhookSendRecordFilter filter, + CancellationToken cancellationToken = default) + { + return await ApplyFilter(await GetDbSetAsync(), filter) + .CountAsync(GetCancellationToken(cancellationToken)); + + } + + public async virtual Task> GetListAsync( + WebhookSendRecordFilter filter, + string sorting = "CreationTime", + int maxResultCount = 10, + int skipCount = 10, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await ApplyFilter((await GetDbSetAsync()).IncludeDetails(includeDetails), filter) + .OrderBy(sorting ?? $"{nameof(WebhookSendRecord.CreationTime)} DESC") + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async override Task> WithDetailsAsync() + { + return (await base.WithDetailsAsync()).IncludeDetails(); + } + + protected virtual IQueryable ApplyFilter( + IQueryable queryable, + WebhookSendRecordFilter filter) + { + return queryable + .WhereIf(filter.WebhookEventId.HasValue, x => x.WebhookEventId == filter.WebhookEventId) + .WhereIf(filter.SubscriptionId.HasValue, x => x.WebhookSubscriptionId == filter.SubscriptionId) + .WhereIf(filter.ResponseStatusCode.HasValue, x => x.ResponseStatusCode == filter.ResponseStatusCode) + .WhereIf(filter.BeginCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.BeginCreationTime) >= 0) + .WhereIf(filter.EndCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.EndCreationTime) <= 0) + .WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.Response.Contains(filter.Filter)); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookSubscriptionRepository.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookSubscriptionRepository.cs new file mode 100644 index 000000000..035fc0591 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/EfCoreWebhookSubscriptionRepository.cs @@ -0,0 +1,72 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; + +public class EfCoreWebhookSubscriptionRepository : + EfCoreRepository, + IWebhookSubscriptionRepository +{ + public EfCoreWebhookSubscriptionRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async virtual Task IsSubscribedAsync( + Guid? tenantId, + string webhookUri, + string webhookName, + CancellationToken cancellationToken = default) + { + return await (await GetDbSetAsync()) + .AnyAsync(x => x.TenantId == tenantId && + x.WebhookUri == webhookUri && + x.Webhooks.Contains("\"" + webhookName + "\""), + GetCancellationToken(cancellationToken)); + } + + public async virtual Task GetCountAsync( + WebhookSubscriptionFilter filter, + CancellationToken cancellationToken = default) + { + return await ApplyFilter(await GetDbSetAsync(), filter) + .CountAsync(GetCancellationToken(cancellationToken)); + } + + public async virtual Task> GetListAsync( + WebhookSubscriptionFilter filter, + string sorting = $"{nameof(WebhookSubscription.CreationTime)} DESC", + int maxResultCount = 10, + int skipCount = 0, + CancellationToken cancellationToken = default) + { + return await ApplyFilter(await GetDbSetAsync(), filter) + .OrderBy(sorting ?? $"{nameof(WebhookSubscription.CreationTime)} DESC") + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + protected virtual IQueryable ApplyFilter( + IQueryable queryable, + WebhookSubscriptionFilter filter) + { + return queryable + .WhereIf(filter.TenantId.HasValue, x => x.TenantId == filter.TenantId) + .WhereIf(filter.IsActive.HasValue, x => x.IsActive == filter.IsActive) + .WhereIf(!filter.WebhookUri.IsNullOrWhiteSpace(), x => x.WebhookUri == filter.WebhookUri) + .WhereIf(!filter.Secret.IsNullOrWhiteSpace(), x => x.Secret == filter.Secret) + .WhereIf(!filter.Webhooks.IsNullOrWhiteSpace(), x => x.Webhooks.Contains("\"" + filter.Webhooks + "\"")) + .WhereIf(filter.BeginCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.BeginCreationTime) >= 0) + .WhereIf(filter.EndCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.EndCreationTime) <= 0) + .WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.WebhookUri.Contains(filter.Filter) || + x.Secret.Contains(filter.Filter) || x.Webhooks.Contains(filter.Filter)); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/IWebhooksManagementDbContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/IWebhooksManagementDbContext.cs new file mode 100644 index 000000000..f6d9c17a8 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/IWebhooksManagementDbContext.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; + +[ConnectionStringName(WebhooksManagementDbProperties.ConnectionStringName)] +public interface IWebhooksManagementDbContext : IEfCoreDbContext +{ +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementDbContext.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementDbContext.cs new file mode 100644 index 000000000..de73188aa --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementDbContext.cs @@ -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, IWebhooksManagementDbContext +{ + public WebhooksManagementDbContext( + DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureWebhooksManagement(); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementDbContextModelCreatingExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementDbContextModelCreatingExtensions.cs new file mode 100644 index 000000000..a7b9a96e7 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementDbContextModelCreatingExtensions.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore; +using System; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; + +public static class WebhooksManagementDbContextModelCreatingExtensions +{ + public static void ConfigureWebhooksManagement( + this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new WebhooksManagementModelBuilderConfigurationOptions( + WebhooksManagementDbProperties.DbTablePrefix, + WebhooksManagementDbProperties.DbSchema + ); + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Events", options.Schema); + + b.Property(p => p.WebhookName) + .IsRequired() + .HasColumnName(nameof(WebhookEventRecord.WebhookName)) + .HasMaxLength(WebhookEventRecordConsts.MaxWebhookNameLength); + b.Property(p => p.Data) + .HasColumnName(nameof(WebhookEventRecord.Data)) + .HasMaxLength(WebhookEventRecordConsts.MaxDataLength); + + b.ConfigureByConvention(); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "SendAttempts", options.Schema); + + b.Property(p => p.Response) + .HasColumnName(nameof(WebhookSendRecord.Response)) + .HasMaxLength(WebhookSendRecordConsts.MaxResponseLength); + + b.ConfigureByConvention(); + + b.HasOne(p => p.WebhookEvent) + .WithOne() + .HasForeignKey(fk => fk.WebhookEventId) + .HasPrincipalKey(pk => pk.Id ); + + b.HasIndex(p => p.WebhookEventId) + .IsUnique(false); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Subscriptions", options.Schema); + + b.Property(p => p.WebhookUri) + .IsRequired() + .HasColumnName(nameof(WebhookSubscription.WebhookUri)) + .HasMaxLength(WebhookSubscriptionConsts.MaxWebhookUriLength); + b.Property(p => p.Secret) + .IsRequired() + .HasColumnName(nameof(WebhookSubscription.Secret)) + .HasMaxLength(WebhookSubscriptionConsts.MaxSecretLength); + + b.Property(p => p.Webhooks) + .HasColumnName(nameof(WebhookSubscription.Webhooks)) + .HasMaxLength(WebhookSubscriptionConsts.MaxWebhooksLength); + b.Property(p => p.Headers) + .HasColumnName(nameof(WebhookSubscription.Headers)) + .HasMaxLength(WebhookSubscriptionConsts.MaxHeadersLength); + + b.ConfigureByConvention(); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementEfCoreQueryableExtensions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementEfCoreQueryableExtensions.cs new file mode 100644 index 000000000..c5baeadb9 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementEfCoreQueryableExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using System.Linq; + +namespace LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; + +public static class WebhooksManagementEfCoreQueryableExtensions +{ + // 在此聚合仓储服务的扩展方法 + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.WebhookEvent); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementEntityFrameworkCoreModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..c175c4007 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementEntityFrameworkCoreModule.cs @@ -0,0 +1,23 @@ +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(options => + { + options.AddRepository(); + options.AddRepository(); + options.AddRepository(); + + options.AddDefaultRepositories(); + }); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementModelBuilderConfigurationOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementModelBuilderConfigurationOptions.cs new file mode 100644 index 000000000..dc8a2e564 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore/LINGYUN/Abp/WebhooksManagement/EntityFrameworkCore/WebhooksManagementModelBuilderConfigurationOptions.cs @@ -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) + { + + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/LINGYUN.Abp.WebhooksManagement.HttpApi.Client.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/LINGYUN.Abp.WebhooksManagement.HttpApi.Client.csproj new file mode 100644 index 000000000..5cee3e037 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/LINGYUN.Abp.WebhooksManagement.HttpApi.Client.csproj @@ -0,0 +1,19 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/LINGYUN/Abp/WebhooksManagement/WebhooksManagementHttpApiClientModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/LINGYUN/Abp/WebhooksManagement/WebhooksManagementHttpApiClientModule.cs new file mode 100644 index 000000000..1fff62168 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi.Client/LINGYUN/Abp/WebhooksManagement/WebhooksManagementHttpApiClientModule.cs @@ -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); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/FodyWeavers.xml b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/FodyWeavers.xml new file mode 100644 index 000000000..be0de3a90 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/FodyWeavers.xsd b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/FodyWeavers.xsd new file mode 100644 index 000000000..11da52550 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN.Abp.WebhooksManagement.HttpApi.csproj b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN.Abp.WebhooksManagement.HttpApi.csproj new file mode 100644 index 000000000..e2556eb0b --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN.Abp.WebhooksManagement.HttpApi.csproj @@ -0,0 +1,19 @@ + + + + + + + net6.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookPublishController.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookPublishController.cs new file mode 100644 index 000000000..43c72a8eb --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookPublishController.cs @@ -0,0 +1,27 @@ +using LINGYUN.Abp.WebhooksManagement.Authorization; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp; + +namespace LINGYUN.Abp.WebhooksManagement; + +[RemoteService(Name = WebhooksManagementRemoteServiceConsts.RemoteServiceName)] +[Area(WebhooksManagementRemoteServiceConsts.ModuleName)] +[Authorize(WebhooksManagementPermissions.Publish)] +[Route("api/webhooks/publish")] +public class WebhookPublishController : WebhooksManagementControllerBase, IWebhookPublishAppService +{ + protected IWebhookPublishAppService PublishAppService { get; } + + public WebhookPublishController(IWebhookPublishAppService publishAppService) + { + PublishAppService = publishAppService; + } + + [HttpPost] + public virtual Task PublishAsync(WebhookPublishInput input) + { + return PublishAppService.PublishAsync(input); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordController.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordController.cs new file mode 100644 index 000000000..ad05c5e8d --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookSendRecordController.cs @@ -0,0 +1,44 @@ +using LINGYUN.Abp.WebhooksManagement.Authorization; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +[RemoteService(Name = WebhooksManagementRemoteServiceConsts.RemoteServiceName)] +[Area(WebhooksManagementRemoteServiceConsts.ModuleName)] +[Authorize(WebhooksManagementPermissions.WebhooksSendAttempts.Default)] +[Route("api/webhooks/send-attempts")] +public class WebhookSendRecordController : WebhooksManagementControllerBase, IWebhookSendRecordAppService +{ + protected IWebhookSendRecordAppService SendRecordAppService { get; } + + public WebhookSendRecordController(IWebhookSendRecordAppService sendRecordAppService) + { + SendRecordAppService = sendRecordAppService; + } + + [HttpGet] + [Route("{id}")] + public Task GetAsync(Guid id) + { + return SendRecordAppService.GetAsync(id); + } + + [HttpGet] + public Task> GetListAsync(WebhookSendRecordGetListInput input) + { + return SendRecordAppService.GetListAsync(input); + } + + [HttpPost] + [Route("{id}/resend")] + [Authorize(WebhooksManagementPermissions.WebhooksSendAttempts.Resend)] + public Task ResendAsync(Guid id) + { + return SendRecordAppService.ResendAsync(id); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionController.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionController.cs new file mode 100644 index 000000000..4cabc602e --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhookSubscriptionController.cs @@ -0,0 +1,66 @@ +using LINGYUN.Abp.WebhooksManagement.Authorization; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.WebhooksManagement; + +[RemoteService(Name = WebhooksManagementRemoteServiceConsts.RemoteServiceName)] +[Area(WebhooksManagementRemoteServiceConsts.ModuleName)] +[Authorize(WebhooksManagementPermissions.WebhookSubscription.Default)] +[Route("api/webhooks/subscriptions")] +public class WebhookSubscriptionController : WebhooksManagementControllerBase, IWebhookSubscriptionAppService +{ + protected IWebhookSubscriptionAppService SubscriptionAppService { get; } + + public WebhookSubscriptionController(IWebhookSubscriptionAppService subscriptionAppService) + { + SubscriptionAppService = subscriptionAppService; + } + + [HttpPost] + [Authorize(WebhooksManagementPermissions.WebhookSubscription.Create)] + public Task CreateAsync(WebhookSubscriptionCreateInput input) + { + return SubscriptionAppService.CreateAsync(input); + } + + [HttpDelete] + [Route("{id}")] + [Authorize(WebhooksManagementPermissions.WebhookSubscription.Delete)] + public Task DeleteAsync(Guid id) + { + return SubscriptionAppService.DeleteAsync(id); + } + + [HttpGet] + [Route("{id}")] + public Task GetAsync(Guid id) + { + return SubscriptionAppService.GetAsync(id); + } + + [HttpGet] + public Task> GetListAsync(WebhookSubscriptionGetListInput input) + { + return SubscriptionAppService.GetListAsync(input); + } + + [HttpPut] + [Route("{id}")] + [Authorize(WebhooksManagementPermissions.WebhookSubscription.Update)] + public Task UpdateAsync(Guid id, WebhookSubscriptionUpdateInput input) + { + return SubscriptionAppService.UpdateAsync(id, input); + } + + [HttpGet] + [Route("availables")] + public Task> GetAllAvailableWebhooksAsync() + { + return SubscriptionAppService.GetAllAvailableWebhooksAsync(); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhooksManagementControllerBase.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhooksManagementControllerBase.cs new file mode 100644 index 000000000..5eef82130 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhooksManagementControllerBase.cs @@ -0,0 +1,12 @@ +using LINGYUN.Abp.WebhooksManagement.Localization; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.WebhooksManagement; + +public abstract class WebhooksManagementControllerBase : AbpControllerBase +{ + protected WebhooksManagementControllerBase() + { + LocalizationResource = typeof(WebhooksManagementResource); + } +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhooksManagementHttpApiModule.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhooksManagementHttpApiModule.cs new file mode 100644 index 000000000..e4e4699a7 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.WebhooksManagement.HttpApi/LINGYUN/Abp/WebhooksManagement/WebhooksManagementHttpApiModule.cs @@ -0,0 +1,40 @@ +using LINGYUN.Abp.WebhooksManagement.Localization; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Validation.Localization; + +namespace LINGYUN.Abp.WebhooksManagement; + +[DependsOn( + typeof(AbpAspNetCoreMvcModule), + typeof(WebhooksManagementApplicationContractsModule))] +public class WebhooksManagementHttpApiModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(WebhooksManagementHttpApiModule).Assembly); + }); + + PreConfigure(options => + { + options.AddAssemblyResource( + typeof(WebhooksManagementResource), + typeof(WebhooksManagementApplicationContractsModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpValidationResource)); + }); + } +} diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile index a38f2b0c8..3e9c90691 100644 --- a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile @@ -7,6 +7,10 @@ COPY . /app #东8区 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone +## 有些定时作业可能需要建立独立的数据库连接, 可能跨不同的数据库, 配置一下MSSQL +## 解决连接SqlServer TLS版本过高问题 +RUN sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf +RUN sed -i 's/TLSv1.2/TLSv1.0/g' /usr/lib/ssl/openssl.cnf EXPOSE 80/tcp VOLUME [ "./app/Logs" ] diff --git a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj index 98bc88491..293653249 100644 --- a/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj +++ b/aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj @@ -45,6 +45,8 @@ + + diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Controllers/HomeController.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Controllers/HomeController.cs new file mode 100644 index 000000000..97e94bcfe --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Controllers/HomeController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace LY.MicroService.WebhooksManagement.Controllers; + +public class HomeController : AbpController +{ + public IActionResult Index() + { + return Redirect("/swagger/index.html"); + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/DataSeeder/WebhooksManagementDataSeederWorker.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/DataSeeder/WebhooksManagementDataSeederWorker.cs new file mode 100644 index 000000000..6a683b100 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/DataSeeder/WebhooksManagementDataSeederWorker.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Data; + +namespace LY.MicroService.WebhooksManagement.DataSeeder; + +public class WebhooksManagementDataSeederWorker : BackgroundService +{ + protected IDataSeeder DataSeeder { get; } + + public WebhooksManagementDataSeederWorker(IDataSeeder dataSeeder) + { + DataSeeder = dataSeeder; + } + + protected async override Task ExecuteAsync(CancellationToken stoppingToken) + { + await DataSeeder.SeedAsync(); + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Dockerfile b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Dockerfile new file mode 100644 index 000000000..0d02e98ca --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 +LABEL maintainer="colin.in@foxmail.com" +WORKDIR /app + +COPY . /app + +#�Ϻ�ʱ�� +#ENV TZ=Asia/Shanghai +#RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone + +EXPOSE 80/tcp +VOLUME [ "./app/Logs" ] +VOLUME [ "./app/Modules" ] + +ENTRYPOINT ["dotnet", "LY..WebhooksManagement.HttpApi.Host.dll"] diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EntityFrameworkCore/WebhooksManagementMigrationsDbContext.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EntityFrameworkCore/WebhooksManagementMigrationsDbContext.cs new file mode 100644 index 000000000..d0f8b1cec --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EntityFrameworkCore/WebhooksManagementMigrationsDbContext.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LY.MicroService.WebhooksManagement.EntityFrameworkCore; + +public class WebhooksManagementMigrationsDbContext : AbpDbContext +{ + public WebhooksManagementMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureWebhooksManagement(); + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EntityFrameworkCore/WebhooksManagementMigrationsDbContextFactory.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EntityFrameworkCore/WebhooksManagementMigrationsDbContextFactory.cs new file mode 100644 index 000000000..88755770b --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EntityFrameworkCore/WebhooksManagementMigrationsDbContextFactory.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using System.IO; + +namespace LY.MicroService.WebhooksManagement.EntityFrameworkCore; + +public class WebhooksManagementMigrationsDbContextFactory : IDesignTimeDbContextFactory +{ + public WebhooksManagementMigrationsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + var connectionString = configuration.GetConnectionString("WebhooksManagement"); + + DbContextOptionsBuilder builder = null; + + builder = new DbContextOptionsBuilder() + .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + + return new WebhooksManagementMigrationsDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false) + .AddJsonFile("appsettings.Development.json", optional: true); + + return builder.Build(); + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs new file mode 100644 index 000000000..ded6862ab --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs @@ -0,0 +1,69 @@ +using LINGYUN.Abp.Data.DbMigrator; +using LINGYUN.Abp.MultiTenancy; +using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace LY.MicroService.WebhooksManagement.EventBus.Handlers; + +public class TenantSynchronizer : + IDistributedEventHandler, + ITransientDependency +{ + protected IDataSeeder DataSeeder { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IDbSchemaMigrator DbSchemaMigrator { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + + protected ILogger Logger { get; } + + public TenantSynchronizer( + IDataSeeder dataSeeder, + ICurrentTenant currentTenant, + IDbSchemaMigrator dbSchemaMigrator, + IUnitOfWorkManager unitOfWorkManager, + ILogger logger) + { + DataSeeder = dataSeeder; + CurrentTenant = currentTenant; + DbSchemaMigrator = dbSchemaMigrator; + UnitOfWorkManager = unitOfWorkManager; + + Logger = logger; + } + + /// + /// 租户创建之后需要预置种子数据 + /// + /// + /// + public virtual async Task HandleEventAsync(CreateEventData eventData) + { + using (var unitOfWork = UnitOfWorkManager.Begin()) + { + using (CurrentTenant.Change(eventData.Id, eventData.Name)) + { + Logger.LogInformation("Migrating the new tenant database with WebhooksManagement..."); + // 迁移租户数据 + await DbSchemaMigrator.MigrateAsync( + (connectionString, builder) => + { + builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + + return new WebhooksManagementDbContext(builder.Options); + }); + Logger.LogInformation("Migrated the new tenant database with WebhooksManagement..."); + + await DataSeeder.SeedAsync(new DataSeedContext(eventData.Id)); + + await unitOfWork.SaveChangesAsync(); + } + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj new file mode 100644 index 000000000..78cda36e2 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/LY.MicroService.WebhooksManagement.HttpApi.Host.csproj @@ -0,0 +1,67 @@ + + + + net6.0 + LY.MicroService.WebhooksManagement + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/20220326064231_Add-Module-WebHooks-Management.Designer.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/20220326064231_Add-Module-WebHooks-Management.Designer.cs new file mode 100644 index 000000000..1da62c921 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/20220326064231_Add-Module-WebHooks-Management.Designer.cs @@ -0,0 +1,165 @@ +// +using System; +using LY.MicroService.WebhooksManagement.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LY.MicroService.WebhooksManagement.Migrations +{ + [DbContext(typeof(WebhooksManagementMigrationsDbContext))] + [Migration("20220326064231_Add-Module-WebHooks-Management")] + partial class AddModuleWebHooksManagement + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.MySql) + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookEventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("Data") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Data"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.Property("WebhookName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("WebhookName"); + + b.HasKey("Id"); + + b.ToTable("AbpWebhooksEvents", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookSendRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("Response") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Response"); + + b.Property("ResponseStatusCode") + .HasColumnType("int"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.Property("WebhookEventId") + .HasColumnType("char(36)"); + + b.Property("WebhookSubscriptionId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("WebhookEventId"); + + b.ToTable("AbpWebhooksSendAttempts", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("Headers") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Headers"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Secret") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("Secret"); + + b.Property("TenantId") + .HasColumnType("char(36)"); + + b.Property("WebhookUri") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("WebhookUri"); + + b.Property("Webhooks") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Webhooks"); + + b.HasKey("Id"); + + b.ToTable("AbpWebhooksSubscriptions", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookSendRecord", b => + { + b.HasOne("LINGYUN.Abp.WebhooksManagement.WebhookEventRecord", "WebhookEvent") + .WithOne() + .HasForeignKey("LINGYUN.Abp.WebhooksManagement.WebhookSendRecord", "WebhookEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebhookEvent"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/20220326064231_Add-Module-WebHooks-Management.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/20220326064231_Add-Module-WebHooks-Management.cs new file mode 100644 index 000000000..e11276c96 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/20220326064231_Add-Module-WebHooks-Management.cs @@ -0,0 +1,103 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LY.MicroService.WebhooksManagement.Migrations +{ + public partial class AddModuleWebHooksManagement : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "AbpWebhooksEvents", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + TenantId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + WebhookName = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Data = table.Column(type: "longtext", maxLength: 2147483647, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationTime = table.Column(type: "datetime(6)", nullable: false), + DeletionTime = table.Column(type: "datetime(6)", nullable: true), + IsDeleted = table.Column(type: "tinyint(1)", nullable: false, defaultValue: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpWebhooksEvents", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "AbpWebhooksSubscriptions", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + TenantId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + WebhookUri = table.Column(type: "varchar(255)", maxLength: 255, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Secret = table.Column(type: "varchar(128)", maxLength: 128, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IsActive = table.Column(type: "tinyint(1)", nullable: false), + Webhooks = table.Column(type: "longtext", maxLength: 2147483647, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Headers = table.Column(type: "longtext", maxLength: 2147483647, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationTime = table.Column(type: "datetime(6)", nullable: false), + CreatorId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci") + }, + constraints: table => + { + table.PrimaryKey("PK_AbpWebhooksSubscriptions", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "AbpWebhooksSendAttempts", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + TenantId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + WebhookEventId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + WebhookSubscriptionId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Response = table.Column(type: "longtext", maxLength: 2147483647, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ResponseStatusCode = table.Column(type: "int", nullable: true), + CreationTime = table.Column(type: "datetime(6)", nullable: false), + LastModificationTime = table.Column(type: "datetime(6)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpWebhooksSendAttempts", x => x.Id); + table.ForeignKey( + name: "FK_AbpWebhooksSendAttempts_AbpWebhooksEvents_WebhookEventId", + column: x => x.WebhookEventId, + principalTable: "AbpWebhooksEvents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_AbpWebhooksSendAttempts_WebhookEventId", + table: "AbpWebhooksSendAttempts", + column: "WebhookEventId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpWebhooksSendAttempts"); + + migrationBuilder.DropTable( + name: "AbpWebhooksSubscriptions"); + + migrationBuilder.DropTable( + name: "AbpWebhooksEvents"); + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/WebhooksManagementMigrationsDbContextModelSnapshot.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/WebhooksManagementMigrationsDbContextModelSnapshot.cs new file mode 100644 index 000000000..6acaca8a1 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Migrations/WebhooksManagementMigrationsDbContextModelSnapshot.cs @@ -0,0 +1,163 @@ +// +using System; +using LY.MicroService.WebhooksManagement.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LY.MicroService.WebhooksManagement.Migrations +{ + [DbContext(typeof(WebhooksManagementMigrationsDbContext))] + partial class WebhooksManagementMigrationsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.MySql) + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookEventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("Data") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Data"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)") + .HasColumnName("DeletionTime"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.Property("WebhookName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("WebhookName"); + + b.HasKey("Id"); + + b.ToTable("AbpWebhooksEvents", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookSendRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("Response") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Response"); + + b.Property("ResponseStatusCode") + .HasColumnType("int"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.Property("WebhookEventId") + .HasColumnType("char(36)"); + + b.Property("WebhookSubscriptionId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("WebhookEventId"); + + b.ToTable("AbpWebhooksSendAttempts", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("Headers") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Headers"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Secret") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("Secret"); + + b.Property("TenantId") + .HasColumnType("char(36)"); + + b.Property("WebhookUri") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("WebhookUri"); + + b.Property("Webhooks") + .HasMaxLength(2147483647) + .HasColumnType("longtext") + .HasColumnName("Webhooks"); + + b.HasKey("Id"); + + b.ToTable("AbpWebhooksSubscriptions", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.WebhooksManagement.WebhookSendRecord", b => + { + b.HasOne("LINGYUN.Abp.WebhooksManagement.WebhookEventRecord", "WebhookEvent") + .WithOne() + .HasForeignKey("LINGYUN.Abp.WebhooksManagement.WebhookSendRecord", "WebhookEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebhookEvent"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Program.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Program.cs new file mode 100644 index 000000000..26693716a --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Program.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.IO; +using Volo.Abp.Modularity.PlugIns; + +namespace LY.MicroService.WebhooksManagement; + +public class Program +{ + public async static Task Main(string[] args) + { + try + { + Log.Information("Starting web host."); + + var builder = WebApplication.CreateBuilder(args); + builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .ConfigureAppConfiguration((context, config) => + { + var configuration = config.Build(); + if (configuration.GetSection("AgileConfig").Exists()) + { + config.AddAgileConfig(new AgileConfig.Client.ConfigClient(configuration)); + } + }) + .UseSerilog((context, provider, config) => + { + config.ReadFrom.Configuration(context.Configuration); + }); + await builder.AddApplicationAsync(options => + { + // 搜索 Modules 目录下所有文件作为插件 + // 取消显示引用所有其他项目的模块,改为通过插件的形式引用 + var pluginFolder = Path.Combine( + Directory.GetCurrentDirectory(), "Modules"); + DirectoryHelper.CreateIfNotExists(pluginFolder); + options.PlugInSources.AddFolder( + pluginFolder, + SearchOption.AllDirectories); + }); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); + return 0; + } + finally + { + Log.CloseAndFlush(); + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Properties/launchSettings.json b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Properties/launchSettings.json new file mode 100644 index 000000000..7e93c505e --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/Properties/launchSettings.json @@ -0,0 +1,21 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20890", + "sslPort": 0 + } + }, + "profiles": { + "LY.MicroService.WebhooksManagement.HttpApi.Host": { + "commandName": "Project", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://127.0.0.1:30045", + "dotnetRunMessages": "true" + } + } +} \ No newline at end of file diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/TenantHeaderParamter.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/TenantHeaderParamter.cs new file mode 100644 index 000000000..492957abe --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/TenantHeaderParamter.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Collections.Generic; +using Volo.Abp.MultiTenancy; + +namespace LY.MicroService.WebhooksManagement; + +public class TenantHeaderParamter : IOperationFilter +{ + private readonly AbpMultiTenancyOptions _options; + public TenantHeaderParamter( + IOptions options) + { + _options = options.Value; + } + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + if (_options.IsEnabled) + { + operation.Parameters = operation.Parameters ?? new List(); + operation.Parameters.Add(new OpenApiParameter + { + Name = TenantResolverConsts.DefaultTenantKey, + In = ParameterLocation.Header, + Description = "Tenant Id/Name", + Required = false + }); + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.Configure.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.Configure.cs new file mode 100644 index 000000000..ddb24ddb0 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.Configure.cs @@ -0,0 +1,312 @@ +using DotNetCore.CAP; +using LINGYUN.Abp.Dapr.Client.DynamicProxying; +using LINGYUN.Abp.ExceptionHandling; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.Wrapper; +using Medallion.Threading; +using Medallion.Threading.Redis; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using StackExchange.Redis; +using System; +using System.Text.Encodings.Web; +using System.Text.Unicode; +using Volo.Abp; +using Volo.Abp.Auditing; +using Volo.Abp.Caching; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; +using Volo.Abp.VirtualFileSystem; + +namespace LY.MicroService.WebhooksManagement; + +public partial class WebhooksManagementHttpApiHostModule +{ + protected const string ApplicationName = "WebhooksManagement"; + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + private void PreConfigureFeature() + { + OneTimeRunner.Run(() => + { + GlobalFeatureManager.Instance.Modules.Editions().EnableAll(); + }); + } + + private void PreConfigureApp() + { + AbpSerilogEnrichersConsts.ApplicationName = ApplicationName; + + PreConfigure(options => + { + // 以开放端口区别 + options.SnowflakeIdOptions.WorkerId = 30045; + options.SnowflakeIdOptions.WorkerIdBits = 5; + options.SnowflakeIdOptions.DatacenterId = 1; + }); + } + + private void PreConfigureCAP(IConfiguration configuration) + { + PreConfigure(options => + { + options + .UseMySql(sqlOptions => + { + configuration.GetSection("CAP:MySql").Bind(sqlOptions); + }) + .UseRabbitMQ(rabbitMQOptions => + { + configuration.GetSection("CAP:RabbitMQ").Bind(rabbitMQOptions); + }) + .UseDashboard(); + }); + } + + private void ConfigureDbContext() + { + // 配置Ef + Configure(options => + { + options.UseMySQL(); + //options.Configure(cfg => + //{ + // cfg.UseMySQL(); + // cfg.DbContextOptions.EnableSensitiveDataLogging(); + //}); + }); + } + + private void ConfigureJsonSerializer() + { + // 解决某些不支持类型的序列化 + Configure(options => + { + options.DefaultDateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + }); + // 中文序列化的编码问题 + Configure(options => + { + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + }); + } + + private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration) + { + if (configuration.GetSection("DistributedLock").Exists()) + { + var redis = ConnectionMultiplexer.Connect(configuration["DistributedLock:Redis:Configuration"]); + services.AddSingleton(_ => new RedisDistributedSynchronizationProvider(redis.GetDatabase())); + } + } + + private void ConfigureOpenTelemetry(IServiceCollection services, IConfiguration configuration) + { + if (configuration.GetSection("OpenTelemetry").Exists()) + { + services.AddOpenTelemetryTracing(cfg => + { + cfg.AddSource(ApplicationName) + .SetResourceBuilder( + ResourceBuilder.CreateDefault().AddService(ApplicationName)) + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation() + .AddEntityFrameworkCoreInstrumentation() + .AddCapInstrumentation() + .AddZipkinExporter(zipKinOptions => + { + var endpoint = configuration["OpenTelemetry:ZipKin:Endpoint"]; + if (!endpoint.IsNullOrWhiteSpace()) + { + zipKinOptions.Endpoint = new Uri(configuration["OpenTelemetry:ZipKin:Endpoint"]); + } + }); + }); + } + } + + private void ConfigureExceptionHandling() + { + // 自定义需要处理的异常 + Configure(options => + { + // 加入需要处理的异常类型 + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + options.Handlers.Add(); + }); + // 自定义需要发送邮件通知的异常类型 + Configure(options => + { + // 是否发送堆栈信息 + options.SendStackTrace = true; + // 未指定异常接收者的默认接收邮件 + // 指定自己的邮件地址 + }); + } + + private void ConfigureAuditing(IConfiguration configuration) + { + Configure(options => + { + options.ApplicationName = ApplicationName; + // 是否启用实体变更记录 + var entitiesChangedConfig = configuration.GetSection("App:TrackingEntitiesChanged"); + if (entitiesChangedConfig.Exists() && entitiesChangedConfig.Get()) + { + options + .EntityHistorySelectors + .AddAllEntities(); + } + }); + } + + private void ConfigureCaching(IConfiguration configuration) + { + Configure(options => + { + // 最好统一命名,不然某个缓存变动其他应用服务有例外发生 + options.KeyPrefix = "LINGYUN.Abp.Application"; + // 滑动过期30天 + options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30d); + // 绝对过期60天 + options.GlobalCacheEntryOptions.AbsoluteExpiration = DateTimeOffset.Now.AddDays(60d); + }); + + Configure(options => + { + var redisConfig = ConfigurationOptions.Parse(options.Configuration); + options.ConfigurationOptions = redisConfig; + options.InstanceName = configuration["Redis:InstanceName"]; + }); + } + + private void ConfigureVirtualFileSystem() + { + Configure(options => + { + options.FileSets.AddEmbedded("LY.MicroService.WebhooksManagement"); + }); + } + + private void ConfigureMultiTenancy(IConfiguration configuration) + { + // 多租户 + Configure(options => + { + options.IsEnabled = true; + }); + + var tenantResolveCfg = configuration.GetSection("App:Domains"); + if (tenantResolveCfg.Exists()) + { + Configure(options => + { + var domains = tenantResolveCfg.Get(); + foreach (var domain in domains) + { + options.AddDomainTenantResolver(domain); + } + }); + } + } + + private void ConfigureSwagger(IServiceCollection services) + { + // Swagger + services.AddSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "WebhooksManagement API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Scheme = "bearer", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT" + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } + }, + new string[] { } + } + }); + options.OperationFilter(); + }); + } + + private void ConfigureLocalization() + { + // 支持本地化语言类型 + Configure(options => + { + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + // 动态语言支持 + options.Resources.AddDynamic(); + }); + } + + private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.Audience = configuration["AuthServer:ApiName"]; + }); + + if (!isDevelopment) + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + services + .AddDataProtection() + .SetApplicationName("LINGYUN.Abp.Application") + .PersistKeysToStackExchangeRedis(redis, "LINGYUN.Abp.Application:DataProtection:Protection-Keys"); + } + } + + private void ConfigureWrapper() + { + Configure(options => + { + // 取消注释包装结果 + // options.IsEnabled = true; + }); + + Configure(options => + { + // dapr服务间调用发送不需要包装结果的请求头 + options.ProxyRequestActions.Add( + (appId, httpRequestMessage) => + { + httpRequestMessage.Headers.TryAddWithoutValidation(AbpHttpWrapConsts.AbpDontWrapResult, "true"); + }); + }); + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.DataSeeder.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.DataSeeder.cs new file mode 100644 index 000000000..ddbbf21ba --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.DataSeeder.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using LY.MicroService.WebhooksManagement.DataSeeder; + +namespace LY.MicroService.WebhooksManagement; + +public partial class WebhooksManagementHttpApiHostModule +{ + private static void ConfigureSeedWorker(IServiceCollection services, bool isDevelopment = false) + { + if (isDevelopment) + { + services.AddHostedService(); + } + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs new file mode 100644 index 000000000..fe0d5379e --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/WebhooksManagementHttpApiHostModule.cs @@ -0,0 +1,125 @@ +using DotNetCore.CAP; +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; +using LINGYUN.Abp.AuditLogging.Elasticsearch; +using LINGYUN.Abp.EventBus.CAP; +using LINGYUN.Abp.ExceptionHandling.Emailing; +using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore; +using LINGYUN.Abp.Saas.EntityFrameworkCore; +using LINGYUN.Abp.Serilog.Enrichers.Application; +using LINGYUN.Abp.Serilog.Enrichers.UniqueId; +using LINGYUN.Abp.Webhooks.Identity; +using LINGYUN.Abp.Webhooks.Saas; +using LINGYUN.Abp.WebhooksManagement; +using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Volo.Abp; +using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.Autofac; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EntityFrameworkCore.MySQL; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Http.Client.IdentityModel.Web; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.Swashbuckle; + +namespace LY.MicroService.WebhooksManagement; + +[DependsOn( + typeof(AbpSerilogEnrichersApplicationModule), + typeof(AbpSerilogEnrichersUniqueIdModule), + typeof(AbpAuditLoggingElasticsearchModule), + typeof(AbpAspNetCoreSerilogModule), + typeof(WebhooksManagementApplicationModule), + typeof(WebhooksManagementHttpApiModule), + typeof(WebhooksManagementEntityFrameworkCoreModule), + typeof(AbpWebhooksIdentityModule), + typeof(AbpWebhooksSaasModule), + typeof(AbpEntityFrameworkCoreMySQLModule), + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpEmailingExceptionHandlingModule), + typeof(AbpCAPEventBusModule), + typeof(AbpHttpClientIdentityModelWebModule), + typeof(AbpAspNetCoreMultiTenancyModule), + typeof(AbpSaasEntityFrameworkCoreModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpLocalizationManagementEntityFrameworkCoreModule), + typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpDistributedLockingModule), + typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpSwashbuckleModule), + typeof(AbpAutofacModule) + )] +public partial class WebhooksManagementHttpApiHostModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + PreConfigureApp(); + PreConfigureFeature(); + PreConfigureCAP(configuration); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + ConfigureWrapper(); + ConfigureDbContext(); + ConfigureLocalization(); + ConfigureJsonSerializer(); + ConfigureExceptionHandling(); + ConfigureVirtualFileSystem(); + ConfigureCaching(configuration); + ConfigureAuditing(configuration); + ConfigureMultiTenancy(configuration); + ConfigureSwagger(context.Services); + ConfigureOpenTelemetry(context.Services, configuration); + ConfigureDistributedLock(context.Services, configuration); + ConfigureSeedWorker(context.Services, hostingEnvironment.IsDevelopment()); + ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); + + context.Services.AddAlwaysAllowAuthorization(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + app.UseCorrelationId(); + app.UseStaticFiles(); + app.UseRouting(); + app.UseCors(); + app.UseAuthentication(); + app.UseJwtTokenMiddleware(); + app.UseMultiTenancy(); + app.UseMapRequestLocalization(); + app.UseAuthorization(); + app.UseCapDashboard(); + app.UseSwagger(); + app.UseAbpSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API"); + + var configuration = context.GetConfiguration(); + options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); + options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); + options.OAuthScopes("WebhooksManagement"); + }); + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + app.UseConfiguredEndpoints(); + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/appsettings.Development.json b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/appsettings.Development.json new file mode 100644 index 000000000..4a816c0c1 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/appsettings.Development.json @@ -0,0 +1,116 @@ +{ + "AgileConfig": { + "env": "DEV", + "appId": "LINGYUN.Abp.WebhooksManagement", + "secret": "1q2w3E*", + "nodes": "http://127.0.0.1:15000", + "name": "LINGYUN.Abp.WebhooksManagement", + "tag": "LINGYUN.Abp.WebhooksManagement" + }, + "App": { + "TrackingEntitiesChanged": true + }, + "ConnectionStrings": { + "Default": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "WebhooksManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpSaas": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpFeatureManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpPermissionManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456", + "AbpSettingManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" + }, + "DistributedLock": { + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=15" + } + }, + "OpenTelemetry": { + "ZipKin": { + "Endpoint": "http://127.0.0.1:9411/api/v2/spans" + } + }, + "RemoteServices": {}, + "IdentityClients": { + "InternalServiceClient": { + "Authority": "http://127.0.0.1:44385", + "RequireHttps": false, + "GrantType": "client_credentials", + "Scope": "lingyun-abp-application", + "ClientId": "InternalServiceClient", + "ClientSecret": "1q2w3E*" + } + }, + "CAP": { + "EventBus": { + "DefaultGroupName": "WebhooksManagement", + "GroupNamePrefix": "Dev", + "Version": "v1", + "FailedRetryInterval": 300, + "FailedRetryCount": 10 + }, + "MySql": { + "TableNamePrefix": "wkm", + "ConnectionString": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456" + }, + "RabbitMQ": { + "HostName": "127.0.0.1", + "Port": 5672, + "UserName": "guest", + "Password": "guest", + "ExchangeName": "LINGYUN.Abp.Application", + "VirtualHost": "/" + } + }, + "Redis": { + "Configuration": "127.0.0.1,defaultDatabase=10", + "InstanceName": "LINGYUN.Abp.Application" + }, + "AuthServer": { + "Authority": "http://127.0.0.1:44385/", + "ApiName": "lingyun-abp-application", + "SwaggerClientId": "InternalServiceClient", + "SwaggerClientSecret": "1q2w3E*" + }, + "Logging": { + "Serilog": { + "Elasticsearch": { + "IndexFormat": "abp.dev.logging-{0:yyyy.MM.dd}" + } + } + }, + "AuditLogging": { + "Elasticsearch": { + "IndexPrefix": "abp.dev.auditing" + } + }, + "Elasticsearch": { + "NodeUris": "http://127.0.0.1:9200" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "System": "Warning", + "Microsoft": "Warning", + "DotNetCore": "Debug" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://127.0.0.1:9200", + "indexFormat": "abp.dev.logging-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true, + "autoRegisterTemplateVersion": "ESv7" + } + } + ] + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/appsettings.json b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/appsettings.json new file mode 100644 index 000000000..67e1bc4bd --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/appsettings.json @@ -0,0 +1,80 @@ +{ + "StringEncryption": { + "DefaultPassPhrase": "s46c5q55nxpeS8Ra", + "InitVectorBytes": "s83ng0abvd02js84", + "DefaultSalt": "sf&5)s3#" + }, + "AllowedHosts": "*", + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft.EntityFrameworkCore": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithProcessId", "WithThreadId", "WithEnvironmentName", "WithMachineName", "WithApplicationName", "WithUniqueId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "initialMinimumLevel": "Verbose", + "standardErrorFromLevel": "Verbose", + "restrictedToMinimumLevel": "Verbose", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Debug-.log", + "restrictedToMinimumLevel": "Debug", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Info-.log", + "restrictedToMinimumLevel": "Information", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Warn-.log", + "restrictedToMinimumLevel": "Warning", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Error-.log", + "restrictedToMinimumLevel": "Error", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "Logs/Fatal-.log", + "restrictedToMinimumLevel": "Fatal", + "rollingInterval": "Day", + "fileSizeLimitBytes": 5242880, + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] [{ProcessId}] [{ThreadId}] - {Message:lj}{NewLine}{Exception}" + } + } + ] + } +} diff --git a/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/dapr.sh b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/dapr.sh new file mode 100644 index 000000000..ddfd59d01 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.WebhooksManagement.HttpApi.Host/dapr.sh @@ -0,0 +1 @@ +dapr run --app-id WebhooksManagement --app-port 57264 -H 63751 -- dotnet run --no-build \ No newline at end of file