diff --git a/Elsa.sln b/Elsa.sln index 2a6facee6..56353aed3 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -241,7 +241,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.InvokeWorkflow EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Persistence.EntityFramework.PostgreSql", "src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.PostgreSql\Elsa.Persistence.EntityFramework.PostgreSql.csproj", "{E3EA6449-28EC-48E4-91C4-E82DE08A7DD9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Telnyx", "src\activities\Elsa.Activities.Telnyx\Elsa.Activities.Telnyx.csproj", "{93878BAD-855D-48D1-97ED-C776EC54E19E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Telnyx", "src\activities\Elsa.Activities.Telnyx\Elsa.Activities.Telnyx.csproj", "{93878BAD-855D-48D1-97ED-C776EC54E19E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.Webhooks", "src\activities\Elsa.Activities.Webhooks\Elsa.Activities.Webhooks.csproj", "{2B67E954-3B04-402D-A9A7-AAAB1D6C3215}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -585,6 +587,10 @@ Global {93878BAD-855D-48D1-97ED-C776EC54E19E}.Debug|Any CPU.Build.0 = Debug|Any CPU {93878BAD-855D-48D1-97ED-C776EC54E19E}.Release|Any CPU.ActiveCfg = Release|Any CPU {93878BAD-855D-48D1-97ED-C776EC54E19E}.Release|Any CPU.Build.0 = Release|Any CPU + {2B67E954-3B04-402D-A9A7-AAAB1D6C3215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B67E954-3B04-402D-A9A7-AAAB1D6C3215}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B67E954-3B04-402D-A9A7-AAAB1D6C3215}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B67E954-3B04-402D-A9A7-AAAB1D6C3215}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -701,6 +707,7 @@ Global {346A3A87-A839-499A-A143-3D267A951905} = {22E75696-6FE9-436A-9097-EE21C603F818} {E3EA6449-28EC-48E4-91C4-E82DE08A7DD9} = {C865B0FD-E505-48F0-BFAF-0D4D7C1B5CA1} {93878BAD-855D-48D1-97ED-C776EC54E19E} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180} + {2B67E954-3B04-402D-A9A7-AAAB1D6C3215} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/src/activities/Elsa.Activities.Webhooks/ActivityTypes/WebhookActivityTypeProvider.cs b/src/activities/Elsa.Activities.Webhooks/ActivityTypes/WebhookActivityTypeProvider.cs new file mode 100644 index 000000000..49c8ee5d1 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/ActivityTypes/WebhookActivityTypeProvider.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Http; +using Elsa.Activities.Webhooks.Models; +using Elsa.Activities.Webhooks.Persistence; +using Elsa.ActivityProviders; +using Elsa.Design; +using Elsa.Metadata; +using Elsa.Persistence.Specifications; +using Elsa.Services; +using Elsa.Services.Models; +using Newtonsoft.Json.Linq; + +namespace Elsa.Activities.Webhooks.ActivityTypes +{ + public class WebhookActivityTypeProvider : IActivityTypeProvider + { + private const string WebhookActivityCategory = "Webhooks"; + + private readonly IWebhookDefinitionStore _webhookDefinitionStore; + private readonly IActivityActivator _activityActivator; + + public WebhookActivityTypeProvider( + IWebhookDefinitionStore webhookDefinitionStore, + IActivityActivator activityActivator) + { + _webhookDefinitionStore = webhookDefinitionStore; + _activityActivator = activityActivator; + } + + public async ValueTask> GetActivityTypesAsync(CancellationToken cancellationToken = default) + { + var specification = Specification.All; + var definitions = await _webhookDefinitionStore.FindManyAsync(specification, cancellationToken: cancellationToken); + + var activityTypes = new List(); + foreach (var definition in definitions) + { + var activity = CreateWebhookActivityType(definition); + activityTypes.Add(activity); + } + + return activityTypes; + } + + private ActivityType CreateWebhookActivityType(WebhookDefinition webhook) + { + var typeName = webhook.Name; + var displayName = webhook.Name; + + var descriptor = new ActivityDescriptor + { + Type = typeName, + DisplayName = displayName, + Category = WebhookActivityCategory, + Outcomes = new[] { OutcomeNames.Done }, + Traits = ActivityTraits.Trigger, + Properties = new[] + { + new ActivityPropertyDescriptor( + "RequestMethod", + ActivityPropertyUIHints.Dropdown, + "Request Method", + "Specify what request method this webhook should handle. Leave empty to handle both GET and POST requests", + new[] { "", "GET", "POST" }) + } + }; + + async ValueTask ActivateActivityAsync(ActivityExecutionContext context) + { + var activity = await _activityActivator.ActivateActivityAsync(context); + + activity.Path = webhook.Path; + activity.ReadContent = true; + activity.TargetType = webhook.PayloadTypeName is not null and not "" ? Type.GetType(webhook.PayloadTypeName) : throw new Exception($"Type {webhook.PayloadTypeName} not found"); + return activity; + } + + return new ActivityType + { + TypeName = webhook.Name, + Type = typeof(HttpEndpoint), + Description = webhook.Description is not null and not "" ? webhook.Description : $"A webhook at {webhook.Path}", + DisplayName = webhook.Name, + ActivateAsync = ActivateActivityAsync, + Describe = () => descriptor + }; + } + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Webhooks/Elsa.Activities.Webhooks.csproj b/src/activities/Elsa.Activities.Webhooks/Elsa.Activities.Webhooks.csproj new file mode 100644 index 000000000..20fba83d8 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Elsa.Activities.Webhooks.csproj @@ -0,0 +1,23 @@ + + + + + + + netstandard2.1 + + Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application. + This package provides the Webhooks activities. + + elsa, workflows + + + + + + + + + + + diff --git a/src/activities/Elsa.Activities.Webhooks/Extensions/ElsaOptionsExtensions.cs b/src/activities/Elsa.Activities.Webhooks/Extensions/ElsaOptionsExtensions.cs new file mode 100644 index 000000000..79ceb86f4 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Extensions/ElsaOptionsExtensions.cs @@ -0,0 +1,34 @@ +using System; +using Elsa; +using Elsa.Activities.Webhooks; +using Elsa.Activities.Webhooks.ActivityTypes; +using Elsa.Activities.Webhooks.Options; +using Elsa.Activities.Webhooks.Persistence; +using Elsa.Activities.Webhooks.Persistence.Decorators; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ElsaOptionsExtensions + { + public static ElsaOptions AddWebhooks( + this ElsaOptions elsaOptions, + Action? configure = default) + { + var services = elsaOptions.Services; + + // Configure Webhooks. + var options = new WebhookOptions(); + configure?.Invoke(options); + + // Services. + services + .AddActivityTypeProvider() + .AddScoped(options.WebhookDefinitionStoreFactory); + + services.Decorate(); + + return elsaOptions; + } + } +} diff --git a/src/activities/Elsa.Activities.Webhooks/FodyWeavers.xml b/src/activities/Elsa.Activities.Webhooks/FodyWeavers.xml new file mode 100644 index 000000000..4c4041f8a --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Webhooks/Models/WebhookDefinition.cs b/src/activities/Elsa.Activities.Webhooks/Models/WebhookDefinition.cs new file mode 100644 index 000000000..41d1cc5a4 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Models/WebhookDefinition.cs @@ -0,0 +1,19 @@ +using System; +using Elsa.Models; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Activities.Webhooks.Models +{ + public class WebhookDefinition : Entity, ITenantScope + { + public string Name { get; set; } = default!; + + public PathString Path { get; set; } + + public string? Description { get; set; } + + public string? PayloadTypeName { get; set; } + + public string? TenantId { get; set; } + } +} diff --git a/src/activities/Elsa.Activities.Webhooks/Options/WebhookOptions.cs b/src/activities/Elsa.Activities.Webhooks/Options/WebhookOptions.cs new file mode 100644 index 000000000..e9e8d8abf --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Options/WebhookOptions.cs @@ -0,0 +1,23 @@ +using System; +using Elsa.Activities.Webhooks.Persistence; +using Elsa.Activities.Webhooks.Persistence.InMemory; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Activities.Webhooks.Options +{ + public class WebhookOptions + { + public WebhookOptions() + { + WebhookDefinitionStoreFactory = provider => ActivatorUtilities.CreateInstance(provider); + } + + internal Func WebhookDefinitionStoreFactory { get; set; } + + public WebhookOptions UseWebhookDefinitionStore(Func factory) + { + WebhookDefinitionStoreFactory = factory; + return this; + } + } +} diff --git a/src/activities/Elsa.Activities.Webhooks/Persistence/Decorators/InitializingStoreBase.cs b/src/activities/Elsa.Activities.Webhooks/Persistence/Decorators/InitializingStoreBase.cs new file mode 100644 index 000000000..3b066906e --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Persistence/Decorators/InitializingStoreBase.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Persistence.Specifications; +using Elsa.Services; + +namespace Elsa.Activities.Webhooks.Persistence.Decorators +{ + public abstract class InitializingStoreBase : IStore + where T : IEntity + { + private readonly IStore _store; + + protected InitializingStoreBase(IStore store, IIdGenerator idGenerator) + { + _store = store; + IdGenerator = idGenerator; + } + + protected IIdGenerator IdGenerator { get; } + + public async Task SaveAsync(T entity, CancellationToken cancellationToken) + { + entity = Initialize(entity); + await _store.SaveAsync(entity, cancellationToken); + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken) + { + entity = Initialize(entity); + await _store.UpdateAsync(entity, cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + entity = Initialize(entity); + await _store.AddAsync(entity, cancellationToken); + } + + public async Task AddManyAsync(IEnumerable entities, CancellationToken cancellationToken = default) + { + var list = entities.ToList(); + + foreach (var entity in list) + Initialize(entity); + + await _store.AddManyAsync(list, cancellationToken); + } + + public Task DeleteAsync(T entity, CancellationToken cancellationToken) => _store.DeleteAsync(entity, cancellationToken); + public Task DeleteManyAsync(ISpecification specification, CancellationToken cancellationToken) => _store.DeleteManyAsync(specification, cancellationToken); + + public Task> FindManyAsync( + ISpecification specification, + IOrderBy? orderBy, + IPaging? paging, + CancellationToken cancellationToken) => + _store.FindManyAsync(specification, orderBy, paging, cancellationToken); + + public Task CountAsync(ISpecification specification, CancellationToken cancellationToken) => _store.CountAsync(specification, cancellationToken); + + public Task FindAsync(ISpecification specification, CancellationToken cancellationToken) => _store.FindAsync(specification, cancellationToken); + + protected abstract T Initialize(T entity); + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Webhooks/Persistence/Decorators/InitializingWebhookDefinitionStore.cs b/src/activities/Elsa.Activities.Webhooks/Persistence/Decorators/InitializingWebhookDefinitionStore.cs new file mode 100644 index 000000000..406fc0981 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Persistence/Decorators/InitializingWebhookDefinitionStore.cs @@ -0,0 +1,21 @@ +using Elsa.Activities.Webhooks.Models; +using Elsa.Services; + +namespace Elsa.Activities.Webhooks.Persistence.Decorators +{ + public class InitializingWebhookDefinitionStore : InitializingStoreBase, IWebhookDefinitionStore + { + public InitializingWebhookDefinitionStore(IWebhookDefinitionStore store, IIdGenerator idGenerator) + : base(store, idGenerator) + { + } + + protected override WebhookDefinition Initialize(WebhookDefinition webhookDefinition) + { + if (string.IsNullOrWhiteSpace(webhookDefinition.Id)) + webhookDefinition.Id = IdGenerator.Generate(); + + return webhookDefinition; + } + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Webhooks/Persistence/IWebhookDefinitionStore.cs b/src/activities/Elsa.Activities.Webhooks/Persistence/IWebhookDefinitionStore.cs new file mode 100644 index 000000000..9167621f5 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Persistence/IWebhookDefinitionStore.cs @@ -0,0 +1,10 @@ +using System; +using Elsa.Activities.Webhooks.Models; +using Elsa.Persistence; + +namespace Elsa.Activities.Webhooks.Persistence +{ + public interface IWebhookDefinitionStore : IStore + { + } +} diff --git a/src/activities/Elsa.Activities.Webhooks/Persistence/InMemory/InMemoryWebhookStore.cs b/src/activities/Elsa.Activities.Webhooks/Persistence/InMemory/InMemoryWebhookStore.cs new file mode 100644 index 000000000..a3202f93d --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Persistence/InMemory/InMemoryWebhookStore.cs @@ -0,0 +1,14 @@ +using Elsa.Activities.Webhooks.Models; +using Elsa.Persistence.InMemory; +using Elsa.Services; +using Microsoft.Extensions.Caching.Memory; + +namespace Elsa.Activities.Webhooks.Persistence.InMemory +{ + public class InMemoryWebhookStore : InMemoryStore, IWebhookDefinitionStore + { + public InMemoryWebhookStore(IMemoryCache memoryCache, IIdGenerator idGenerator) : base(memoryCache, idGenerator) + { + } + } +} diff --git a/src/activities/Elsa.Activities.Webhooks/Services/IWebhookDefinitionsApi.cs b/src/activities/Elsa.Activities.Webhooks/Services/IWebhookDefinitionsApi.cs new file mode 100644 index 000000000..dd4ee5889 --- /dev/null +++ b/src/activities/Elsa.Activities.Webhooks/Services/IWebhookDefinitionsApi.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Webhooks.Models; +using Refit; + +namespace Elsa.Activities.Webhooks.Services +{ + public interface IWebhookDefinitionsApi + { + [Get("/v1/webhook-definitions/{webhookDefinitionId}")] + Task GetByIdAsync(string webhookDefinitionId, CancellationToken cancellationToken = default); + + [Get("/v1/webhook-definitions")] + Task> ListAsync(CancellationToken cancellationToken = default); + } +} diff --git a/src/clients/Elsa.Client/Elsa.Client.csproj b/src/clients/Elsa.Client/Elsa.Client.csproj index ba75425dd..5fed8fff7 100644 --- a/src/clients/Elsa.Client/Elsa.Client.csproj +++ b/src/clients/Elsa.Client/Elsa.Client.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/src/clients/Elsa.Client/ElsaClient.cs b/src/clients/Elsa.Client/ElsaClient.cs index 97dddf5b6..6bbe84b41 100644 --- a/src/clients/Elsa.Client/ElsaClient.cs +++ b/src/clients/Elsa.Client/ElsaClient.cs @@ -1,20 +1,28 @@ -using Elsa.Client.Services; +using Elsa.Activities.Webhooks.Services; +using Elsa.Client.Services; namespace Elsa.Client { public class ElsaClient : IElsaClient { - public ElsaClient(IActivitiesApi activities, IWorkflowDefinitionsApi workflowDefinitions, IWorkflowRegistryApi workflowRegistry, IWorkflowInstancesApi workflowInstances) + public ElsaClient( + IActivitiesApi activities, + IWorkflowDefinitionsApi workflowDefinitions, + IWorkflowRegistryApi workflowRegistry, + IWorkflowInstancesApi workflowInstances, + IWebhookDefinitionsApi webhookDefinitions) { Activities = activities; WorkflowDefinitions = workflowDefinitions; WorkflowRegistry = workflowRegistry; WorkflowInstances = workflowInstances; + WebhookDefinitions = webhookDefinitions; } public IActivitiesApi Activities { get; } public IWorkflowDefinitionsApi WorkflowDefinitions { get; } public IWorkflowRegistryApi WorkflowRegistry { get; } public IWorkflowInstancesApi WorkflowInstances { get; } + public IWebhookDefinitionsApi WebhookDefinitions { get; } } } \ No newline at end of file diff --git a/src/clients/Elsa.Client/IElsaClient.cs b/src/clients/Elsa.Client/IElsaClient.cs index aa487ab0b..47cb3dd45 100644 --- a/src/clients/Elsa.Client/IElsaClient.cs +++ b/src/clients/Elsa.Client/IElsaClient.cs @@ -1,4 +1,5 @@ -using Elsa.Client.Services; +using Elsa.Activities.Webhooks.Services; +using Elsa.Client.Services; namespace Elsa.Client { @@ -8,5 +9,6 @@ namespace Elsa.Client IWorkflowDefinitionsApi WorkflowDefinitions { get; } IWorkflowRegistryApi WorkflowRegistry { get; } IWorkflowInstancesApi WorkflowInstances { get; } + IWebhookDefinitionsApi WebhookDefinitions { get; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Metadata/ActivityPropertyDescriptor.cs b/src/core/Elsa.Abstractions/Metadata/ActivityPropertyDescriptor.cs index b3c3ddb3d..02942283a 100644 --- a/src/core/Elsa.Abstractions/Metadata/ActivityPropertyDescriptor.cs +++ b/src/core/Elsa.Abstractions/Metadata/ActivityPropertyDescriptor.cs @@ -6,7 +6,7 @@ namespace Elsa.Metadata { } - public ActivityPropertyDescriptor(string name, string uiHint, string label, string? hint, object? options, string? category, object? defaultValue) + public ActivityPropertyDescriptor(string name, string uiHint, string label, string? hint = default, object? options = default, string? category = default, object? defaultValue = default) { Name = name; UIHint = uiHint; diff --git a/src/core/Elsa.Abstractions/Services/IActivityActivator.cs b/src/core/Elsa.Abstractions/Services/IActivityActivator.cs index d4d384600..82cb03462 100644 --- a/src/core/Elsa.Abstractions/Services/IActivityActivator.cs +++ b/src/core/Elsa.Abstractions/Services/IActivityActivator.cs @@ -8,4 +8,9 @@ namespace Elsa.Services { Task ActivateActivityAsync(ActivityExecutionContext context, Type type); } + + public static class ActivityActivatorExtensions + { + public static async Task ActivateActivityAsync(this IActivityActivator activityActivator, ActivityExecutionContext context) where T : IActivity => (T) await activityActivator.ActivateActivityAsync(context, typeof(T)); + } } \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ApplicationBuilderExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ApplicationBuilderExtensions.cs index b146b9009..87287ece5 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ApplicationBuilderExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ApplicationBuilderExtensions.cs @@ -1,4 +1,4 @@ -using ElsaDashboard.Shared.Rpc; +using ElsaDashboard.Shared.Rpc; using Microsoft.AspNetCore.Builder; namespace ElsaDashboard.Backend.Extensions @@ -15,6 +15,7 @@ namespace ElsaDashboard.Backend.Extensions .MapGrpcEndpoint() .MapGrpcEndpoint() .MapGrpcEndpoint() + .MapGrpcEndpoint() ); } } diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs index 2c19b4783..ddaec9102 100644 --- a/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO.Compression; using Elsa.Client.Extensions; using Elsa.Client.Options; @@ -24,6 +24,7 @@ namespace ElsaDashboard.Backend.Extensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WebhookDefinitionService.cs b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WebhookDefinitionService.cs new file mode 100644 index 000000000..ac4438861 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Backend/Rpc/WebhookDefinitionService.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Elsa.Activities.Webhooks.Models; +using Elsa.Client; +using ElsaDashboard.Shared.Rpc; +using ProtoBuf.Grpc; + +namespace ElsaDashboard.Backend.Rpc +{ + public class WebhookDefinitionService : IWebhookDefinitionService + { + private readonly IElsaClient _elsaClient; + + public WebhookDefinitionService(IElsaClient elsaClient) + { + _elsaClient = elsaClient; + } + + public Task> ListAsync(CallContext context = default) => + _elsaClient.WebhookDefinitions.ListAsync(context.CancellationToken); + + public Task GetByIdAsync(GetWebhookDefinitionByIdRequest request, CallContext context = default) => + _elsaClient.WebhookDefinitions.GetByIdAsync(request.WebhookDefinitionId, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/WebhookDefinition/Models/GetWebhookDefinitionByIdRequest.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/WebhookDefinition/Models/GetWebhookDefinitionByIdRequest.cs new file mode 100644 index 000000000..d443cc9fb --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/WebhookDefinition/Models/GetWebhookDefinitionByIdRequest.cs @@ -0,0 +1,19 @@ +using ProtoBuf; + +namespace ElsaDashboard.Shared.Rpc +{ + [ProtoContract] + public class GetWebhookDefinitionByIdRequest + { + public GetWebhookDefinitionByIdRequest() + { + } + + public GetWebhookDefinitionByIdRequest(string webhookDefinitionId) + { + WebhookDefinitionId = webhookDefinitionId; + } + + [ProtoMember(1)] public string WebhookDefinitionId { get; set; } = default!; + } +} \ No newline at end of file diff --git a/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/WebhookDefinition/Services/IWebhookDefinitionService.cs b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/WebhookDefinition/Services/IWebhookDefinitionService.cs new file mode 100644 index 000000000..5009065f4 --- /dev/null +++ b/src/dashboards/blazor/ElsaDashboard.Shared/Rpc/WebhookDefinition/Services/IWebhookDefinitionService.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Elsa.Activities.Webhooks.Models; +using ProtoBuf.Grpc; +using ProtoBuf.Grpc.Configuration; + +namespace ElsaDashboard.Shared.Rpc +{ + [Service] + public interface IWebhookDefinitionService + { + [Operation] + Task> ListAsync(CallContext context = default); + + [Operation] + Task GetByIdAsync(GetWebhookDefinitionByIdRequest request, CallContext context = default); + } +} \ No newline at end of file diff --git a/src/samples/dashboard/ElsaDashboard.Samples.WebAssembly/ElsaDashboard.Samples.WebAssembly.csproj b/src/samples/dashboard/ElsaDashboard.Samples.WebAssembly/ElsaDashboard.Samples.WebAssembly.csproj index 803345236..c3a300546 100644 --- a/src/samples/dashboard/ElsaDashboard.Samples.WebAssembly/ElsaDashboard.Samples.WebAssembly.csproj +++ b/src/samples/dashboard/ElsaDashboard.Samples.WebAssembly/ElsaDashboard.Samples.WebAssembly.csproj @@ -4,6 +4,7 @@ + false net5.0 false diff --git a/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj b/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj index fbb541399..4dcdc16e2 100644 --- a/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj +++ b/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj @@ -25,6 +25,7 @@ + diff --git a/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/Get.cs b/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/Get.cs new file mode 100644 index 000000000..f803bce15 --- /dev/null +++ b/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/Get.cs @@ -0,0 +1,48 @@ +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Webhooks.Models; +using Elsa.Activities.Webhooks.Persistence; +using Elsa.Models; +using Elsa.Persistence.Specifications; +using Elsa.Serialization; +using Elsa.Server.Api.Swagger.Examples; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.Filters; + +namespace Elsa.Server.Api.Endpoints.WebhookDefinitions +{ + [ApiController] + [ApiVersion("1")] + [Route("v{apiVersion:apiVersion}/webhook-definitions/{webhookDefinitionId}")] + [Produces(MediaTypeNames.Application.Json)] + public class Get : Controller + { + private readonly IWebhookDefinitionStore _webhookDefinitionStore; + private readonly IContentSerializer _serializer; + + public Get(IWebhookDefinitionStore webhookDefinitionStore, IContentSerializer serializer) + { + _webhookDefinitionStore = webhookDefinitionStore; + _serializer = serializer; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowDefinition))] + [SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionExample))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerOperation( + Summary = "Returns a single webhook definition.", + Description = "Returns a single webhook definition using the specified webhook definition ID.", + OperationId = "WebhookDefinitions.GetByDefinition", + Tags = new[] { "WebhookDefinitions" }) + ] + public async Task Handle(string webhookDefinitionId, CancellationToken cancellationToken = default) + { + var webhookDefinition = await _webhookDefinitionStore.FindAsync(new EntityIdSpecification(webhookDefinitionId), cancellationToken); + return webhookDefinition == null ? (IActionResult) NotFound() : Json(webhookDefinition, _serializer.GetSettings()); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs b/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs new file mode 100644 index 000000000..3be7efaa8 --- /dev/null +++ b/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Webhooks.Models; +using Elsa.Activities.Webhooks.Persistence; +using Elsa.Models; +using Elsa.Persistence.Specifications; +using Elsa.Serialization; +using Elsa.Server.Api.Models; +using Elsa.Server.Api.Swagger.Examples; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.Filters; + +namespace Elsa.Server.Api.Endpoints.WebhookDefinitions +{ + [ApiController] + [ApiVersion("1")] + [Route("v{apiVersion:apiVersion}/webhook-definitions")] + [Produces(MediaTypeNames.Application.Json)] + public class List : Controller + { + private readonly IWebhookDefinitionStore _webhookDefinitionStore; + private readonly IContentSerializer _serializer; + + public List(IWebhookDefinitionStore webhookDefinitionStore, IContentSerializer serializer) + { + _webhookDefinitionStore = webhookDefinitionStore; + _serializer = serializer; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [SwaggerResponseExample(StatusCodes.Status200OK, typeof(WebhookDefinitionListExample))] + [SwaggerOperation( + Summary = "Returns a list of webhook definitions.", + Description = "Returns a list of webhook definitions.", + OperationId = "WebhookDefinitions.List", + Tags = new[] { "WebhookDefinitions" }) + ] + public async Task>> Handle(CancellationToken cancellationToken = default) + { + var specification = Specification.All; + var items = await _webhookDefinitionStore.FindManyAsync(specification, cancellationToken: cancellationToken); + + return Json(items, _serializer.GetSettings()); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Swagger/Examples/WebhookDefinitionExample.cs b/src/server/Elsa.Server.Api/Swagger/Examples/WebhookDefinitionExample.cs new file mode 100644 index 000000000..5485be203 --- /dev/null +++ b/src/server/Elsa.Server.Api/Swagger/Examples/WebhookDefinitionExample.cs @@ -0,0 +1,21 @@ +using System; +using Elsa.Activities.Webhooks.Models; +using Swashbuckle.AspNetCore.Filters; + +namespace Elsa.Server.Api.Swagger.Examples +{ + public class WebhookDefinitionExample : IExamplesProvider + { + public WebhookDefinition GetExamples() + { + return new() + { + Id = Guid.NewGuid().ToString("N"), + Path = "/sample-path", + Description = "Sample description", + PayloadTypeName = "PayloadType", + TenantId = "tenant", + }; + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Swagger/Examples/WebhookDefinitionListExample.cs b/src/server/Elsa.Server.Api/Swagger/Examples/WebhookDefinitionListExample.cs new file mode 100644 index 000000000..745046748 --- /dev/null +++ b/src/server/Elsa.Server.Api/Swagger/Examples/WebhookDefinitionListExample.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using Elsa.Activities.Webhooks.Models; +using Swashbuckle.AspNetCore.Filters; + +namespace Elsa.Server.Api.Swagger.Examples +{ + public class WebhookDefinitionListExample : IExamplesProvider> + { + public IEnumerable GetExamples() + { + var result = Enumerable.Range(1, 3).Select(_ => new WebhookDefinitionExample().GetExamples()).ToList(); + return result; + } + } +} \ No newline at end of file