Incremental work

This commit is contained in:
Sipke Schoorstra 2022-08-26 21:59:17 +02:00
parent 58663b4b2f
commit 078f21cf61
214 changed files with 1982 additions and 1716 deletions

View file

@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D
ProjectSection(SolutionItems) = preProject
README.md = README.md
docker-compose.yml = docker-compose.yml
common.props = common.props
configureawait.props = configureawait.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}"
@ -78,7 +80,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AllInOne.Web", "src\bu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.WorkflowServer.Web", "src\bundles\Elsa.WorkflowServer.Web\Elsa.WorkflowServer.Web.csproj", "{3C2752A7-EEBC-4ED4-B1BF-0FCF6E071BC2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.Abstractions", "src\common\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj", "{6D590FA1-898B-429B-AE85-DE16BE691246}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.Common", "src\common\Elsa.Persistence.Common\Elsa.Persistence.Common.csproj", "{6D590FA1-898B-429B-AE85-DE16BE691246}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.EntityFrameworkCore.Common", "src\common\Elsa.Persistence.EntityFrameworkCore.Common\Elsa.Persistence.EntityFrameworkCore.Common.csproj", "{B962750E-C2B1-4F5A-B3C5-6C64CAF1ED2E}"
EndProject
@ -100,8 +102,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Features", "src\common
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MemoryCache", "src\modules\Elsa.MemoryCache\Elsa.MemoryCache.csproj", "{2570418D-1674-4DAE-9A24-9146D522450C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AspNetCore", "src\modules\Elsa.AspNetCore\Elsa.AspNetCore.csproj", "{2CFF3041-BB6A-45EA-9716-7BA531BA6479}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Expressions", "src\modules\Elsa.Expressions\Elsa.Expressions.csproj", "{80529478-A383-4FEA-B744-C71264969E9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Mediator", "src\modules\Elsa.Mediator\Elsa.Mediator.csproj", "{40ED82D8-A7BB-4D37-9157-3176EDA3E119}"
@ -132,6 +132,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "worker", "worker", "{389D40
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Worker1", "src\samples\Elsa.Samples.Worker1\Elsa.Samples.Worker1.csproj", "{197223FB-2472-442C-BD10-2E1F931285BC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Models.Common", "src\common\Elsa.Models.Common\Elsa.Models.Common.csproj", "{81BDD2FB-810E-4526-8290-80BA2F4FC473}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -290,10 +292,6 @@ Global
{2570418D-1674-4DAE-9A24-9146D522450C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2570418D-1674-4DAE-9A24-9146D522450C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2570418D-1674-4DAE-9A24-9146D522450C}.Release|Any CPU.Build.0 = Release|Any CPU
{2CFF3041-BB6A-45EA-9716-7BA531BA6479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CFF3041-BB6A-45EA-9716-7BA531BA6479}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CFF3041-BB6A-45EA-9716-7BA531BA6479}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CFF3041-BB6A-45EA-9716-7BA531BA6479}.Release|Any CPU.Build.0 = Release|Any CPU
{80529478-A383-4FEA-B744-C71264969E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80529478-A383-4FEA-B744-C71264969E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80529478-A383-4FEA-B744-C71264969E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -342,6 +340,10 @@ Global
{517B703A-7653-4036-AAB5-7B4293D39D0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{197223FB-2472-442C-BD10-2E1F931285BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{197223FB-2472-442C-BD10-2E1F931285BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81BDD2FB-810E-4526-8290-80BA2F4FC473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81BDD2FB-810E-4526-8290-80BA2F4FC473}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81BDD2FB-810E-4526-8290-80BA2F4FC473}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81BDD2FB-810E-4526-8290-80BA2F4FC473}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
@ -385,7 +387,6 @@ Global
{8A050229-DB79-4E0B-9AFF-7565E87F2954} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{C237BA1A-3A7D-4AB2-BE09-2696F3C082A4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
{2570418D-1674-4DAE-9A24-9146D522450C} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{2CFF3041-BB6A-45EA-9716-7BA531BA6479} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{80529478-A383-4FEA-B744-C71264969E9A} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{40ED82D8-A7BB-4D37-9157-3176EDA3E119} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{80FF5821-E831-450D-AA0C-C76D07D878F4} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
@ -402,5 +403,6 @@ Global
{517B703A-7653-4036-AAB5-7B4293D39D0F} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4}
{F599E891-598E-402F-A9B9-95D0B1C661F6} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{26087ED2-9249-4FF3-8869-C11C405E441A} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{81BDD2FB-810E-4526-8290-80BA2F4FC473} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
EndGlobalSection
EndGlobal

30
common.props Normal file
View file

@ -0,0 +1,30 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Authors>Elsa Contributors</Authors>
<Copyright>2022</Copyright>
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIconUrl>https://elsaworkflows.io/nuget-icon.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisModeDocumentation>Default</AnalysisModeDocumentation>
<AnalysisLevelDocumentation>latest</AnalysisLevelDocumentation>
<!-- https://github.com/dotnet/sourcelink -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

9
configureawait.props Normal file
View file

@ -0,0 +1,9 @@
<Project>
<ItemGroup>
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.1" PrivateAssets="All" />
<PackageReference Include="Fody" Version="6.6.3">
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View file

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>

View file

@ -1,5 +1,4 @@
using Elsa.Workflows.Api.Extensions;
using Elsa.AspNetCore.Extensions;
using Elsa.Extensions;
using Elsa.Jobs.Extensions;
using Elsa.Http;
@ -46,7 +45,6 @@ services
.UseLiquid()
.UseHttp()
.UseCustomActivities()
.UseMvc()
);
// Register serialization configurator for configuring what types to allow to be serialized.

View file

@ -0,0 +1,3 @@
# All In One
This project represents an Elsa application that hosts both the workflow server as well as the dashboard.

View file

@ -4,6 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>

View file

@ -1,35 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Elsa.Api.Common;
using Elsa.Api.Common.Options;
using Elsa.Api.Common.Services;
using Elsa.WorkflowServer.Web.Models;
using FastEndpoints.Security;
using Microsoft.Extensions.Options;
namespace Elsa.WorkflowServer.Web.Implementations;
public class CustomAccessTokenIssuer : IAccessTokenIssuer
{
private readonly AccessTokenOptions _options;
public CustomAccessTokenIssuer(IOptions<AccessTokenOptions> options)
{
_options = options.Value;
}
public ValueTask<string> CreateTokenAsync(IDictionary<string, string> credentials, object? context, CancellationToken cancellationToken = default)
{
var user = context as User;
var fullName = user!.FullName;
var jwtToken = JWTBearer.CreateToken(
signingKey: _options.SigningKey,
roles: new[] { RoleNames.Admin },
permissions: new[] { PermissionNames.Everything },
claims: new[] { new Claim(JwtRegisteredClaimNames.Name, fullName) }
);
return new(jwtToken);
}
}

View file

@ -1,17 +0,0 @@
using Elsa.Api.Common.Models;
using Elsa.Api.Common.Services;
using Elsa.WorkflowServer.Web.Models;
namespace Elsa.WorkflowServer.Web.Implementations;
public class CustomCredentialsValidator : ICredentialsValidator
{
public ValueTask<ValidateCredentialsResult> ValidateCredentialsAsync(IDictionary<string, string> credentials, CancellationToken cancellationToken = default)
{
var isValid = credentials.TryGetValue("user", out var user) && user == "admin" && credentials.TryGetValue("password", out var password) && password == "admin";
return !isValid
? new(ValidateCredentialsResult.Invalid())
: new(ValidateCredentialsResult.Valid(new User("Superman")));
}
}

View file

@ -2,10 +2,6 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.ActivityDefinitions.EntityFrameworkCore.Extensions;
using Elsa.ActivityDefinitions.EntityFrameworkCore.Sqlite;
using Elsa.Api.Common;
using Elsa.Api.Common.Features;
using Elsa.Api.Common.Options;
using Elsa.AspNetCore.Extensions;
using Elsa.Extensions;
using Elsa.Features.Extensions;
using Elsa.Http;
@ -32,24 +28,12 @@ using Elsa.Workflows.Persistence.EntityFrameworkCore.Extensions;
using Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite;
using Elsa.Workflows.Persistence.Extensions;
using Elsa.Workflows.Runtime.Extensions;
using Elsa.WorkflowServer.Web.Implementations;
using Elsa.WorkflowServer.Web.Jobs;
using FastEndpoints;
using FastEndpoints.Security;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var accessTokenOptions = new AccessTokenOptions();
configuration.GetSection("AccessTokens").Bind(accessTokenOptions);
// Global control over Elsa API endpoints security.
ApiSecurityOptions.AllowAnonymous = !builder.Environment.IsProduction();
ApiSecurityOptions.ValidateRoles = builder.Environment.IsProduction();
// Add application-specific services.
services.AddSingleton<CustomCredentialsValidator>();
services.AddSingleton<CustomAccessTokenIssuer>();
// Add Elsa services.
services
@ -70,12 +54,6 @@ services
.AddActivity<RunJavaScript>()
.AddActivity<Event>()
)
.Use<CommonApiFeature>(feature =>
{
feature.TokenSigningKey = accessTokenOptions.SigningKey;
feature.CredentialsValidator = sp => sp.GetRequiredService<CustomCredentialsValidator>();
feature.AccessTokenIssuer = sp => sp.GetRequiredService<CustomAccessTokenIssuer>();
})
.UseJobActivities()
.UseScheduling()
.UseWorkflowPersistence(p => p.UseEntityFrameworkCore(ef => ef.UseSqlite()))
@ -85,11 +63,9 @@ services
.UseLabels(labels => labels.UseEntityFrameworkCore(ef => ef.UseSqlite()))
.UseCustomActivities(feature => feature.UseEntityFrameworkCore(ef => ef.UseSqlite()))
.UseHttp()
.UseMvc()
);
services.AddFastEndpoints();
services.AddAuthenticationJWTBearer(accessTokenOptions.SigningKey);
services.AddHealthChecks();
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
@ -161,9 +137,9 @@ Task SerializeRequestAsync(HttpResponse httpResponse, object dto, string content
app.UseFastEndpoints(config =>
{
config.RoutingOptions = routing => routing.Prefix = "elsa/api";
config.RequestDeserializer = DeserializeRequestAsync;
config.ResponseSerializer = SerializeRequestAsync;
config.Endpoints.RoutePrefix = "elsa/api";
config.Serializer.RequestDeserializer = DeserializeRequestAsync;
config.Serializer.ResponseSerializer = SerializeRequestAsync;
});
// Register Elsa middleware.

View file

@ -0,0 +1,3 @@
# Server
This project represents an Elsa application that hosts workflows and exposes API endpoints to manage & execute workflows.

View file

@ -6,8 +6,5 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AccessTokens": {
"SigningKey": "ultra-secure-key"
}
"AllowedHosts": "*"
}

View file

@ -1,10 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<Description>
Bundles the most commonly-used packages when building an Elsa workflows application.
</Description>
<RootNamespace>Elsa</RootNamespace>
<LangVersion>latest</LangVersion>
<PackageTags>elsa</PackageTags>
</PropertyGroup>
<ItemGroup>

View file

@ -1,4 +1,3 @@
using System;
using Elsa.Features;
using Elsa.Features.Extensions;
using Elsa.Features.Services;
@ -6,21 +5,20 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Extensions;
/// <summary>
/// Provides extension methods to <see cref="IServiceCollection"/>.
/// </summary>
public static class DependencyInjectionExtensions
{
/// <summary>
/// Creates a new Elsa module and adds the <see cref="ElsaFeature"/> to it.
/// </summary>
public static IServiceCollection AddElsa(this IServiceCollection services, Action<IModule>? configure = default)
{
var serviceConfiguration = services.ConfigureElsa();
configure?.Invoke(serviceConfiguration);
serviceConfiguration.Apply();
return services;
}
public static IModule ConfigureElsa(this IServiceCollection services)
{
var module = services.CreateModule();
module.Configure<ElsaFeature>();
return module;
configure?.Invoke(module);
module.Apply();
return services;
}
}

View file

@ -1,11 +1,16 @@
using System;
using Elsa.Workflows.Core.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Extensions;
/// <summary>
/// Provides extension methods to the <see cref="IServiceProvider"/>.
/// </summary>
public static class ServiceProviderExtensions
{
/// <summary>
/// Configure the default activity execution pipeline.
/// </summary>
public static IServiceProvider ConfigureDefaultActivityExecutionPipeline(this IServiceProvider services, Action<IActivityExecutionBuilder> setup)
{
var pipeline = services.GetRequiredService<IActivityExecutionPipeline>();
@ -13,6 +18,9 @@ public static class ServiceProviderExtensions
return services;
}
/// <summary>
/// Configure the default workflow execution pipeline.
/// </summary>
public static IServiceProvider ConfigureDefaultWorkflowExecutionPipeline(this IServiceProvider services, Action<IWorkflowExecutionBuilder> setup)
{
var pipeline = services.GetRequiredService<IWorkflowExecutionPipeline>();

View file

@ -1,4 +1,3 @@
using Elsa.Api.Common.Features;
using Elsa.Features.Abstractions;
using Elsa.Features.Attributes;
using Elsa.Features.Services;
@ -10,15 +9,18 @@ using Elsa.Workflows.Runtime.Features;
namespace Elsa.Features;
/// <summary>
/// Represents Elsa as a feature of the system.
/// </summary>
[DependsOn(typeof(MediatorFeature))]
[DependsOn(typeof(WorkflowsFeature))]
[DependsOn(typeof(FlowchartFeature))]
[DependsOn(typeof(WorkflowPersistenceFeature))]
[DependsOn(typeof(WorkflowRuntimeFeature))]
[DependsOn(typeof(WorkflowManagementFeature))]
[DependsOn(typeof(CommonApiFeature))]
public class ElsaFeature : FeatureBase
{
/// <inheritdoc />
public ElsaFeature(IModule module) : base(module)
{
}

View file

@ -1,9 +0,0 @@
namespace Elsa.Api.Common;
public static class ApiSecurityOptions
{
public static bool AllowAnonymous = true;
public static bool ValidatePermissions = false;
public static bool ValidatePolicies = false;
public static bool ValidateRoles = false;
}

View file

@ -1,19 +0,0 @@
namespace Elsa.Api.Common;
public static class PermissionNames
{
/// <summary>
/// Allow access to all API endpoints.
/// </summary>
public const string Everything = "Everything";
}
public static class PolicyNames
{
public const string Admin = "Admin";
}
public static class RoleNames
{
public const string Admin = "Admin";
}

View file

@ -1,20 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Provides common features to modules that expose API endpoints.
</Description>
<RootNamespace>Elsa</RootNamespace>
<PackageTags>elsa, api</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FastEndpoints" Version="4.3.1" />
<PackageReference Include="FastEndpoints.Security" Version="4.3.1" />
<PackageReference Include="FastEndpoints.Swagger" Version="4.3.1" />
<PackageReference Include="FastEndpoints" Version="5.0.0" />
<PackageReference Include="FastEndpoints.Security" Version="5.0.0" />
<PackageReference Include="FastEndpoints.Swagger" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.Features\Elsa.Features.csproj" />
<ProjectReference Include="..\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj" />
<ProjectReference Include="..\Elsa.Models.Common\Elsa.Models.Common.csproj" />
</ItemGroup>
</Project>

View file

@ -1,26 +0,0 @@
using Elsa.Api.Common.Implementations;
using Elsa.Api.Common.Options;
using Elsa.Api.Common.Services;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Api.Common.Features;
public class CommonApiFeature : FeatureBase
{
public CommonApiFeature(IModule module) : base(module)
{
}
public string TokenSigningKey { get; set; } = default!;
public Func<IServiceProvider, ICredentialsValidator> CredentialsValidator { get; set; } = ActivatorUtilities.GetServiceOrCreateInstance<AllowAnyCredentialsValidator>;
public Func<IServiceProvider, IAccessTokenIssuer> AccessTokenIssuer { get; set; } = ActivatorUtilities.GetServiceOrCreateInstance<AdminAccessTokenIssuer>;
public override void Apply()
{
Services.Configure<AccessTokenOptions>(options => options.SigningKey = TokenSigningKey);
Services.AddSingleton(sp => CredentialsValidator(sp));
Services.AddSingleton(sp => AccessTokenIssuer(sp));
}
}

View file

@ -1,29 +0,0 @@
using Elsa.Api.Common.Options;
using Elsa.Api.Common.Services;
using FastEndpoints.Security;
using Microsoft.Extensions.Options;
namespace Elsa.Api.Common.Implementations;
/// <summary>
/// Creates access tokens that grant full access to all Elsa API endpoints by means of the Admin role and Everything permissions.
/// </summary>
public class AdminAccessTokenIssuer : IAccessTokenIssuer
{
private readonly AccessTokenOptions _options;
public AdminAccessTokenIssuer(IOptions<AccessTokenOptions> options)
{
_options = options.Value;
}
public ValueTask<string> CreateTokenAsync(IDictionary<string, string> credentials, object? context, CancellationToken cancellationToken = default)
{
var jwtToken = JWTBearer.CreateToken(
signingKey: _options.SigningKey,
roles: new[] { RoleNames.Admin },
permissions: new[] { PermissionNames.Everything });
return new(jwtToken);
}
}

View file

@ -1,9 +0,0 @@
using Elsa.Api.Common.Models;
using Elsa.Api.Common.Services;
namespace Elsa.Api.Common.Implementations;
public class AllowAnyCredentialsValidator : ICredentialsValidator
{
public ValueTask<ValidateCredentialsResult> ValidateCredentialsAsync(IDictionary<string, string> credentials, CancellationToken cancellationToken = default) => new(ValidateCredentialsResult.Valid());
}

View file

@ -1,6 +0,0 @@
namespace Elsa.Api.Common.Models;
public class EndpointConfiguration
{
public ICollection<string> Policies { get; } = new List<string>();
}

View file

@ -1,6 +1,4 @@
using Elsa.Persistence.Common.Models;
namespace Elsa.Api.Common.Models;
namespace Elsa.Models;
public class PagedListResponse<T>
{

View file

@ -1,4 +1,4 @@
namespace Elsa.Api.Common.Models;
namespace Elsa.Models;
public record ValidateCredentialsResult(bool IsValid, object? Context = null)
{

View file

@ -1,6 +0,0 @@
namespace Elsa.Api.Common.Options;
public class AccessTokenOptions
{
public string SigningKey { get; set; } = default!;
}

View file

@ -1,45 +0,0 @@
using FastEndpoints;
namespace Elsa.Api.Common;
public class ProtectedEndpoint<TRequest, TResponse> : Endpoint<TRequest, TResponse> where TRequest : notnull, new() where TResponse : notnull, new()
{
protected void ConfigureSecurity(IEnumerable<string> permissions, IEnumerable<string> policies, IEnumerable<string> roles)
{
if (ApiSecurityOptions.AllowAnonymous)
{
AllowAnonymous();
return;
}
if (ApiSecurityOptions.ValidatePermissions)
Permissions(new[] { PermissionNames.Everything }.Concat(permissions).ToArray());
if (ApiSecurityOptions.ValidatePolicies)
Policies(new[] { PolicyNames.Admin }.Concat(policies).ToArray());
if (ApiSecurityOptions.ValidateRoles)
Roles(new[] { RoleNames.Admin }.Concat(roles).ToArray());
}
}
public class ProtectedEndpoint<TRequest> : Endpoint<TRequest> where TRequest : notnull, new()
{
protected void ConfigureSecurity(IEnumerable<string> permissions, IEnumerable<string> policies, IEnumerable<string> roles)
{
if (ApiSecurityOptions.AllowAnonymous)
{
AllowAnonymous();
return;
}
if (ApiSecurityOptions.ValidatePermissions)
Permissions(new[] { PermissionNames.Everything }.Concat(permissions).ToArray());
if (ApiSecurityOptions.ValidatePolicies)
Policies(new[] { PolicyNames.Admin }.Concat(policies).ToArray());
if (ApiSecurityOptions.ValidateRoles)
Roles(new[] { RoleNames.Admin }.Concat(roles).ToArray());
}
}

View file

@ -1,9 +0,0 @@
namespace Elsa.Api.Common.Services;
/// <summary>
/// Implement this to issue new security tokens based on the specified credentials.
/// </summary>
public interface IAccessTokenIssuer
{
ValueTask<string> CreateTokenAsync(IDictionary<string, string> credentials, object? context, CancellationToken cancellationToken = default);
}

View file

@ -1,8 +0,0 @@
using Elsa.Api.Common.Models;
namespace Elsa.Api.Common.Services;
public interface ICredentialsValidator
{
ValueTask<ValidateCredentialsResult> ValidateCredentialsAsync(IDictionary<string, string> credentials, CancellationToken cancellationToken = default);
}

View file

@ -3,24 +3,46 @@ using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Features.Abstractions;
/// <summary>
/// Base type for classes that represent a feature.
/// </summary>
public abstract class FeatureBase : IFeature
{
/// <summary>
/// Constructor.
/// </summary>
protected FeatureBase(IModule module)
{
Module = module;
}
/// <summary>
/// The module this feature is a part of.
/// </summary>
public IModule Module { get; }
/// <summary>
/// A reference to the <see cref="IServiceCollection"/> to which services can be added.
/// </summary>
public IServiceCollection Services => Module.Services;
/// <summary>
/// Override this method to configure your feature.
/// </summary>
public virtual void Configure()
{
}
/// <summary>
/// Override this method to register any hosted services provided by your feature.
/// </summary>
public virtual void ConfigureHostedServices()
{
}
/// <summary>
/// Override this to register services with <see cref="Services"/>.
/// </summary>
public virtual void Apply()
{
}

View file

@ -1,9 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Provides utilities to turn your class library into a module that provides features.
</Description>
<PackageTags>elsa, common</PackageTags>
</PropertyGroup>
<ItemGroup>

View file

@ -1,9 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
A small utility library for formatting objects from and to strings.
</Description>
<PackageTags>elsa, common</PackageTags>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Description>
Provides common models used by various modules.
</Description>
<RootNamespace>Elsa</RootNamespace>
<PackageTags>elsa, common</PackageTags>
</PropertyGroup>
</Project>

View file

@ -1,4 +1,4 @@
namespace Elsa.Persistence.Common.Models;
namespace Elsa.Models;
public record Page<T>(ICollection<T> Items, long TotalCount);

View file

@ -1,4 +1,4 @@
namespace Elsa.Persistence.Common.Models;
namespace Elsa.Models;
public record PageArgs(int? Page, int? PageSize)
{

View file

@ -1,7 +1,7 @@
using System.ComponentModel;
using Elsa.Persistence.Common.TypeConverters;
using Elsa.TypeConverters;
namespace Elsa.Persistence.Common.Models;
namespace Elsa.Models;
[TypeConverter(typeof(VersionOptionsTypeConverter))]
public struct VersionOptions

View file

@ -1,8 +1,8 @@
using System.ComponentModel;
using System.Globalization;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
namespace Elsa.Persistence.Common.TypeConverters
namespace Elsa.TypeConverters
{
public class VersionOptionsTypeConverter : TypeConverter
{

View file

@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Elsa.Persistence.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LinqKit" Version="1.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Description>
Provides common features that can be used by persistence modules.
</Description>
<PackageTags>elsa, common, persistence</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LinqKit" Version="1.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.Models.Common\Elsa.Models.Common.csproj" />
</ItemGroup>
</Project>

View file

@ -1,4 +1,4 @@
using Elsa.Persistence.Common.Models;
using Elsa.Models;
namespace Elsa.Persistence.Common.Extensions;

View file

@ -1,5 +1,5 @@
using System.Linq.Expressions;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
namespace Elsa.Persistence.Common.Extensions;

View file

@ -1,6 +1,6 @@
using System.Linq.Expressions;
using Elsa.Models;
using Elsa.Persistence.Common.Entities;
using Elsa.Persistence.Common.Models;
using LinqKit;
namespace Elsa.Persistence.Common.Extensions;

View file

@ -1,9 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Provides common features that can be used by persistence modules using Entity Framework Core.
</Description>
<PackageTags>elsa</PackageTags>
</PropertyGroup>
<ItemGroup>
@ -23,7 +28,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj" />
<ProjectReference Include="..\Elsa.Persistence.Common\Elsa.Persistence.Common.csproj" />
<ProjectReference Include="..\Elsa.Features\Elsa.Features.csproj" />
</ItemGroup>

View file

@ -1,6 +1,6 @@
using System.Linq.Expressions;
using EFCore.BulkExtensions;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
using Microsoft.EntityFrameworkCore;
namespace Elsa.Persistence.EntityFrameworkCore.Common.Extensions;

View file

@ -1,7 +1,7 @@
using System.Linq.Expressions;
using EFCore.BulkExtensions;
using Elsa.Models;
using Elsa.Persistence.Common.Entities;
using Elsa.Persistence.Common.Models;
using Elsa.Persistence.EntityFrameworkCore.Common.Extensions;
using Elsa.Persistence.EntityFrameworkCore.Common.Services;
using Microsoft.EntityFrameworkCore;

View file

@ -1,6 +1,6 @@
using System.Linq.Expressions;
using Elsa.Models;
using Elsa.Persistence.Common.Entities;
using Elsa.Persistence.Common.Models;
namespace Elsa.Persistence.EntityFrameworkCore.Common.Services;

View file

@ -1,9 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Provides abstractions representing a service bus that can be used by other Elsa modules.
Concrete implementations are provided by different packages such as Elsa.MassTransit.
</Description>
<PackageTags>elsa, common, service bus</PackageTags>
</PropertyGroup>
<ItemGroup>

View file

@ -1,13 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props"/>
<Import Project="..\..\..\configureawait.props"/>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Provides migrations specific to SQLite for the Entity Framework Core implementation.
</Description>
<PackageTags>elsa, module, efcore, sqlite</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -1,13 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props"/>
<Import Project="..\..\..\configureawait.props"/>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Provides an Entity Framework Core implementation of the persistence abstractions provided by Elsa.ActivityDefinitions.
</Description>
<PackageTags>elsa, module, efcore</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -2,8 +2,8 @@ using System.Linq.Expressions;
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Models;
using Elsa.ActivityDefinitions.Services;
using Elsa.Models;
using Elsa.Persistence.Common.Extensions;
using Elsa.Persistence.Common.Models;
using Elsa.Persistence.EntityFrameworkCore.Common.Extensions;
using Elsa.Persistence.EntityFrameworkCore.Common.Services;

View file

@ -1,9 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Activity Definitions allow you to manage custom activity definitions that can be created using the workflow designer.
Think reusable mini workflows that can be used as composite activities.
</Description>
<PackageTags>elsa, module</PackageTags>
</PropertyGroup>
<ItemGroup>
@ -17,7 +23,7 @@
<ItemGroup>
<ProjectReference Include="..\..\common\Elsa.Api.Common\Elsa.Api.Common.csproj" />
<ProjectReference Include="..\Elsa.Mediator\Elsa.Mediator.csproj" />
<ProjectReference Include="..\..\common\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj" />
<ProjectReference Include="..\..\common\Elsa.Persistence.Common\Elsa.Persistence.Common.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Persistence\Elsa.Workflows.Persistence.csproj" />

View file

@ -1,8 +1,12 @@
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.BulkDelete;
public static class SecurityConstants
/// <summary>
/// Provides policy names accepted by the <see cref="BulkDelete"/> endpoint.
/// </summary>
public static class Constants
{
public static readonly string[] Policies = { "DeleteActivityDefinitions" };
public static readonly string[] Permissions = { "DeleteActivityDefinitions" };
public static readonly string[] Roles = { "DefinitionsDeleter" };
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "BulkDeleteActivityDefinitions";
}

View file

@ -1,23 +1,29 @@
using Elsa.ActivityDefinitions.Services;
using Elsa.Api.Common;
using FastEndpoints;
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.BulkDelete;
public class BulkDelete : ProtectedEndpoint<Request, Response>
/// <summary>
/// An endpoint that bulk-deletes activity definitions.
/// </summary>
public class BulkDelete : Endpoint<Request, Response>
{
private readonly IActivityDefinitionStore _activityDefinitionStore;
/// <inheritdoc />
public BulkDelete(IActivityDefinitionStore activityDefinitionStore)
{
_activityDefinitionStore = activityDefinitionStore;
}
/// <inheritdoc />
public override void Configure()
{
Post("/bulk-actions/delete/activity-definitions/by-definition-id");
ConfigureSecurity(SecurityConstants.Permissions, SecurityConstants.Policies, SecurityConstants.Roles);
Policies(Constants.PolicyName);
}
/// <inheritdoc />
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
{
var deleted = await _activityDefinitionStore.DeleteByDefinitionIdsAsync(request.DefinitionIds, cancellationToken);

View file

@ -1,11 +1,23 @@
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.BulkDelete;
/// <summary>
/// Represents a bulk-delete activity definitions request.
/// </summary>
public class Request
{
/// <summary>
/// The activity definition IDs to delete.
/// </summary>
public ICollection<string> DefinitionIds { get; set; } = default!;
}
/// <summary>
/// Represents a bulk-delete response.
/// </summary>
public class Response
{
/// <summary>
/// The number of records that were actually deleted.
/// </summary>
public int Deleted { get; set; }
}

View file

@ -1,8 +1,12 @@
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.Delete;
public static class SecurityConstants
/// <summary>
/// Provides policy names accepted by the <see cref="Delete"/> endpoint.
/// </summary>
public static class Constants
{
public static readonly string[] Policies = { "DeleteActivityDefinitions" };
public static readonly string[] Permissions = { "DeleteActivityDefinitions" };
public static readonly string[] Roles = { "DefinitionsDeleter" };
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "DeleteActivityDefinition";
}

View file

@ -1,23 +1,29 @@
using Elsa.ActivityDefinitions.Services;
using Elsa.Api.Common;
using FastEndpoints;
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.Delete;
public class Delete : ProtectedEndpoint<Request>
/// <summary>
/// An endpoint that deletes a specific activity definition by ID.
/// </summary>
public class Delete : Endpoint<Request>
{
private readonly IActivityDefinitionStore _activityDefinitionStore;
/// <inheritdoc />
public Delete(IActivityDefinitionStore activityDefinitionStore)
{
_activityDefinitionStore = activityDefinitionStore;
}
/// <inheritdoc />
public override void Configure()
{
Delete("/activity-definitions/{definitionId}");
ConfigureSecurity(SecurityConstants.Permissions, SecurityConstants.Policies, SecurityConstants.Roles);
Policies(Constants.PolicyName);
}
/// <inheritdoc />
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
await _activityDefinitionStore.DeleteByDefinitionIdAsync(request.DefinitionId, cancellationToken);

View file

@ -1,8 +1,12 @@
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.Get;
public static class SecurityConstants
/// <summary>
/// Provides policy names accepted by the <see cref="Get"/> endpoint.
/// </summary>
public static class Constants
{
public static readonly string[] Policies = { "ReadActivityDefinitions" };
public static readonly string[] Permissions = { "ReadActivityDefinitions" };
public static readonly string[] Roles = { "ActivityDefinitionsReader" };
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "ReadActivityDefinitions";
}

View file

@ -1,20 +1,26 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Models;
using Elsa.ActivityDefinitions.Services;
using Elsa.Api.Common;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
using Elsa.Workflows.Core.Serialization;
using Elsa.Workflows.Core.Services;
using Elsa.Workflows.Management.Mappers;
using FastEndpoints;
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.Get;
public class Get : ProtectedEndpoint<Request, ActivityDefinitionModel>
/// <summary>
/// An endpoint that returns the specified <see cref="ActivityDefinition"/> by ID.
/// </summary>
public class Get : Endpoint<Request, ActivityDefinitionModel>
{
private readonly IActivityDefinitionStore _activityDefinitionStore;
private readonly VariableDefinitionMapper _variableDefinitionMapper;
private readonly SerializerOptionsProvider _serializerOptionsProvider;
/// <inheritdoc />
public Get(IActivityDefinitionStore activityDefinitionStore, VariableDefinitionMapper variableDefinitionMapper, SerializerOptionsProvider serializerOptionsProvider)
{
_activityDefinitionStore = activityDefinitionStore;
@ -22,12 +28,14 @@ public class Get : ProtectedEndpoint<Request, ActivityDefinitionModel>
_serializerOptionsProvider = serializerOptionsProvider;
}
/// <inheritdoc />
public override void Configure()
{
Get("/activity-definitions/{definitionId}");
ConfigureSecurity(SecurityConstants.Permissions, SecurityConstants.Policies, SecurityConstants.Roles);
Policies(Constants.PolicyName);
}
/// <inheritdoc />
public override async Task<ActivityDefinitionModel> ExecuteAsync(Request request, CancellationToken cancellationToken)
{
var parsedVersionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest;

View file

@ -1,8 +1,12 @@
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.List;
public static class SecurityConstants
/// <summary>
/// Provides policy names accepted by the <see cref="List"/> endpoint.
/// </summary>
public static class Constants
{
public static readonly string[] Policies = { "ListActivityDefinitions" };
public static readonly string[] Permissions = { "ListActivityDefinitions" };
public static readonly string[] Roles = { "ActivityDefinitionsViewer" };
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "ListActivityDefinitions";
}

View file

@ -1,26 +1,31 @@
using Elsa.ActivityDefinitions.Models;
using Elsa.ActivityDefinitions.Services;
using Elsa.Api.Common;
using Elsa.Api.Common.Models;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
using FastEndpoints;
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.List;
public class List : ProtectedEndpoint<Request, PagedListResponse<ActivityDefinitionSummary>>
/// <summary>
/// An endpoint that returns a page of <see cref="ActivityDefinitionSummary"/> objects.
/// </summary>
public class List : Endpoint<Request, PagedListResponse<ActivityDefinitionSummary>>
{
private readonly IActivityDefinitionStore _store;
/// <inheritdoc />
public List(IActivityDefinitionStore store)
{
_store = store;
}
/// <inheritdoc />
public override void Configure()
{
Get("/activity-definitions");
ConfigureSecurity(SecurityConstants.Permissions, SecurityConstants.Policies, SecurityConstants.Roles);
Policies(Constants.PolicyName);
}
/// <inheritdoc />
public override async Task<PagedListResponse<ActivityDefinitionSummary>> ExecuteAsync(Request req, CancellationToken ct)
{
var parsedVersionOptions = req.VersionOptions != null ? VersionOptions.FromString(req.VersionOptions) : VersionOptions.Published;

View file

@ -1,8 +1,12 @@
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.Post;
public static class SecurityConstants
/// <summary>
/// Provides policy names accepted by the <see cref="Post"/> endpoint.
/// </summary>
public static class Constants
{
public static readonly string[] Policies = { "CreateActivityDefinitions", "UpdateActivityDefinitions" };
public static readonly string[] Permissions = { "CreateActivityDefinitions", "UpdateActivityDefinitions" };
public static readonly string[] Roles = { "ActivityDefinitionsCreator", "ActivityDefinitionsUpdater" };
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "CreateOrUpdateActivityDefinition";
}

View file

@ -1,19 +1,24 @@
using System.Text.Json;
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Services;
using Elsa.Api.Common;
using Elsa.Workflows.Core.Activities.Flowchart.Activities;
using Elsa.Workflows.Core.Serialization;
using Elsa.Workflows.Management.Mappers;
using Elsa.Workflows.Management.Models;
using FastEndpoints;
namespace Elsa.ActivityDefinitions.Endpoints.ActivityDefinitions.Post;
public class Post : ProtectedEndpoint<Request, Response>
/// <summary>
/// An endpoint that creates or updates <see cref="ActivityDefinition"/> objects.
/// </summary>
public class Post : Endpoint<Request, Response>
{
private readonly SerializerOptionsProvider _serializerOptionsProvider;
private readonly IActivityDefinitionPublisher _activityDefinitionPublisher;
private readonly VariableDefinitionMapper _variableDefinitionMapper;
/// <inheritdoc />
public Post(
SerializerOptionsProvider serializerOptionsProvider,
IActivityDefinitionPublisher activityDefinitionPublisher,
@ -24,12 +29,14 @@ public class Post : ProtectedEndpoint<Request, Response>
_variableDefinitionMapper = variableDefinitionMapper;
}
/// <inheritdoc />
public override void Configure()
{
Post("/activity-definitions");
ConfigureSecurity(List.SecurityConstants.Permissions, List.SecurityConstants.Policies, List.SecurityConstants.Roles);
Policies(Constants.PolicyName);
}
/// <inheritdoc />
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
{
var definitionId = request.DefinitionId;

View file

@ -1,6 +1,6 @@
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Services;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
namespace Elsa.ActivityDefinitions.Extensions;

View file

@ -2,7 +2,7 @@ using Elsa.ActivityDefinitions.Activities;
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Extensions;
using Elsa.ActivityDefinitions.Services;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
using Elsa.Workflows.Core;
using Elsa.Workflows.Core.Models;
using Elsa.Workflows.Management.Services;

View file

@ -1,7 +1,6 @@
using System.Text.Json;
using Elsa.ActivityDefinitions.Activities;
using Elsa.ActivityDefinitions.Services;
using Elsa.Persistence.Common.Models;
using Elsa.Workflows.Core;
using Elsa.Workflows.Core.Activities;
using Elsa.Workflows.Core.Serialization;

View file

@ -4,7 +4,7 @@ using Elsa.ActivityDefinitions.Notifications;
using Elsa.ActivityDefinitions.Services;
using Elsa.Common.Services;
using Elsa.Mediator.Services;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
using Elsa.Workflows.Core.Activities.Flowchart.Activities;
using Elsa.Workflows.Core.Serialization;
using Elsa.Workflows.Core.Services;

View file

@ -1,9 +1,9 @@
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Models;
using Elsa.ActivityDefinitions.Services;
using Elsa.Models;
using Elsa.Persistence.Common.Extensions;
using Elsa.Persistence.Common.Implementations;
using Elsa.Persistence.Common.Models;
namespace Elsa.ActivityDefinitions.Implementations;

View file

@ -1,6 +1,6 @@
using Elsa.ActivityDefinitions.Entities;
using Elsa.ActivityDefinitions.Models;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
namespace Elsa.ActivityDefinitions.Services;

View file

@ -1,17 +0,0 @@
namespace Elsa.AspNetCore.Attributes;
/// <summary>
/// Apply this to a controller that models an API endpoint, where the controller contains a single action.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ApiEndpointAttribute : Attribute
{
public string ControllerName { get; }
public string ActionName { get; }
public ApiEndpointAttribute(string controllerName, string actionName)
{
ControllerName = controllerName;
ActionName = actionName;
}
}

View file

@ -1,24 +0,0 @@
using Elsa.AspNetCore.Attributes;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Elsa.AspNetCore.Conventions;
/// <summary>
/// A custom controller model convention that applies a controller & action name based on the values provided via <see cref="ApiEndpointAttribute"/>.
/// </summary>
public class ApiEndpointAttributeConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
var controllerNameAttribute = controller.Attributes.OfType<ApiEndpointAttribute>().SingleOrDefault();
if (controllerNameAttribute != null)
{
controller.ControllerName = controllerNameAttribute.ControllerName;
var actionModel = controller.Actions.FirstOrDefault();
if (actionModel != null)
actionModel.ActionName = controllerNameAttribute.ActionName;
}
}
}

View file

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FastEndpoints" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\Elsa.Features\Elsa.Features.csproj" />
</ItemGroup>
</Project>

View file

@ -1,13 +0,0 @@
using Elsa.AspNetCore.Features;
using Elsa.Features.Services;
namespace Elsa.AspNetCore.Extensions;
public static class DependencyInjectionExtensions
{
public static IModule UseMvc(this IModule configuration, Action<MvcFeature>? configure = default)
{
configuration.Configure(configure);
return configuration;
}
}

View file

@ -1,29 +0,0 @@
using Elsa.AspNetCore.Parts;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AspNetCore.Extensions;
public static class MvcBuilderExtensions
{
/// <summary>
/// Remove all application parts. This allows for manually controlling what controllers to add.
/// </summary>
public static IMvcBuilder ClearApplicationParts(this IMvcBuilder mvcBuilder) =>
mvcBuilder.ConfigureApplicationPartManager(partManager =>
partManager.ApplicationParts.Clear());
/// <summary>
/// Add application parts from the assembly containing the specified type.
/// </summary>
public static IMvcBuilder AddApplicationPartsFrom<T>(this IMvcBuilder mvcBuilder) =>
mvcBuilder.ConfigureApplicationPartManager(partManager =>
partManager.ApplicationParts.Add(new AssemblyPart(typeof(T).Assembly)));
/// <summary>
/// Add the specified controller types.
/// </summary>
public static IMvcBuilder AddControllerTypes(this IMvcBuilder mvcBuilder, params Type[] controllerTypes) =>
mvcBuilder.ConfigureApplicationPartManager(partManager =>
partManager.ApplicationParts.Add(new TypesPart(controllerTypes)));
}

View file

@ -1,18 +0,0 @@
using Elsa.AspNetCore.Conventions;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AspNetCore.Features;
public class MvcFeature : FeatureBase
{
public MvcFeature(IModule module) : base(module)
{
}
public override void Configure()
{
Services.AddMvc(mvc => mvc.Conventions.Add(new ApiEndpointAttributeConvention()));
}
}

View file

@ -1,9 +0,0 @@
namespace Elsa.AspNetCore.Models;
public record ListModel<T>(ICollection<T> Items);
public static class ListModel
{
public static ListModel<T> Of<T>(ICollection<T> items) => new(items);
public static ListModel<T> Of<T>(IEnumerable<T> items) => new(items.ToList());
}

View file

@ -1,20 +0,0 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
namespace Elsa.AspNetCore.Parts;
/// <summary>
/// A custom <see cref="ApplicationPart"/> that controls what types will be provided to the system.
/// Use this to control what controllers should be made available from other libraries.
/// </summary>
public class TypesPart : ApplicationPart, IApplicationPartTypeProvider
{
public TypesPart(params Type[] types)
{
Types = types.Select(t => t.GetTypeInfo());
}
public override string Name => string.Join(", ", Types.Select(t => t.FullName));
public IEnumerable<TypeInfo> Types { get; }
}

View file

@ -1,6 +1,6 @@
using Elsa.Labels.Entities;
using Elsa.Labels.Services;
using Elsa.Persistence.Common.Models;
using Elsa.Models;
using Elsa.Persistence.EntityFrameworkCore.Common.Extensions;
using Elsa.Persistence.EntityFrameworkCore.Common.Services;

View file

@ -6,18 +6,15 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Open.Linq.AsyncExtensions" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\Elsa.Api.Common\Elsa.Api.Common.csproj" />
<ProjectReference Include="..\Elsa.AspNetCore\Elsa.AspNetCore.csproj" />
<ProjectReference Include="..\Elsa.Mediator\Elsa.Mediator.csproj" />
<ProjectReference Include="..\..\common\Elsa.Persistence.Abstractions\Elsa.Persistence.Abstractions.csproj" />
<ProjectReference Include="..\..\common\Elsa.Persistence.Common\Elsa.Persistence.Common.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
<ProjectReference Include="..\Elsa.Workflows.Persistence\Elsa.Workflows.Persistence.csproj" />

View file

@ -1,48 +0,0 @@
using Elsa.AspNetCore.Attributes;
using Elsa.Labels.Entities;
using Elsa.Labels.Services;
using Elsa.Workflows.Core.Serialization;
using Elsa.Workflows.Core.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Elsa.Labels.Endpoints.Labels;
[Area(AreaNames.Elsa)]
[ApiEndpoint(ControllerNames.Labels, "Create")]
[ProducesResponseType(typeof(Label), StatusCodes.Status201Created)]
public class Create : Controller
{
private readonly ILabelStore _store;
private readonly IIdentityGenerator _identityGenerator;
private readonly SerializerOptionsProvider _serializerOptionsProvider;
// ReSharper disable once ClassNeverInstantiated.Local
private record LabelModel(string Name, string? Description, string? Color);
public Create(ILabelStore store, IIdentityGenerator identityGenerator, SerializerOptionsProvider serializerOptionsProvider)
{
_store = store;
_identityGenerator = identityGenerator;
_serializerOptionsProvider = serializerOptionsProvider;
}
[HttpPost]
public async Task<IActionResult> HandleAsync(CancellationToken cancellationToken)
{
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
var model = (await Request.ReadFromJsonAsync<LabelModel>(serializerOptions, cancellationToken))!;
var label = new Label
{
Id = _identityGenerator.GenerateId(),
Name = model.Name.Trim(),
Description = model.Description?.Trim(),
Color = model.Color?.Trim()
};
await _store.SaveAsync(label, cancellationToken);
return CreatedAtRoute("Elsa.Labels.Get", new { Id = label.Id }, label);
}
}

View file

@ -1,25 +0,0 @@
using Elsa.AspNetCore.Attributes;
using Elsa.Labels.Entities;
using Elsa.Labels.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Elsa.Labels.Endpoints.Labels;
[Area(AreaNames.Elsa)]
[ApiEndpoint(ControllerNames.Labels, "Delete")]
[ProducesResponseType(typeof(Label), StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public class Delete : Controller
{
private readonly ILabelStore _store;
public Delete(ILabelStore store) => _store = store;
[HttpDelete]
public async Task<IActionResult> HandleAsync(string id, CancellationToken cancellationToken)
{
var deleted = await _store.DeleteAsync(id, cancellationToken);
return deleted ? NoContent() : NotFound();
}
}

View file

@ -0,0 +1,12 @@
namespace Elsa.Labels.Endpoints.Labels.Delete;
/// <summary>
/// Provides policy names accepted by the <see cref="Delete"/> endpoint.
/// </summary>
public static class Constants
{
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "DeleteLabel";
}

View file

@ -0,0 +1,27 @@
using Elsa.Labels.Services;
using FastEndpoints;
namespace Elsa.Labels.Endpoints.Labels.Delete;
public class Delete : Endpoint<Request>
{
private readonly ILabelStore _store;
public Delete(ILabelStore store) => _store = store;
public override void Configure()
{
Delete("/labels/{id}");
Policies(Constants.PolicyName);
}
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var deleted = await _store.DeleteAsync(request.Id, cancellationToken);
if (deleted)
await SendNoContentAsync(cancellationToken);
else
await SendNotFoundAsync(cancellationToken);
}
}

View file

@ -0,0 +1,6 @@
namespace Elsa.Labels.Endpoints.Labels.Delete;
public class Request
{
public string Id { get; init; } = default!;
}

View file

@ -1,31 +0,0 @@
using Elsa.AspNetCore.Attributes;
using Elsa.Labels.Entities;
using Elsa.Labels.Services;
using Elsa.Workflows.Core.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Elsa.Labels.Endpoints.Labels;
[Area(AreaNames.Elsa)]
[ApiEndpoint(ControllerNames.Labels, "Get")]
[ProducesResponseType(typeof(Label), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public class Get : Controller
{
private readonly ILabelStore _store;
private readonly SerializerOptionsProvider _serializerOptionsProvider;
public Get(ILabelStore store, SerializerOptionsProvider serializerOptionsProvider)
{
_store = store;
_serializerOptionsProvider = serializerOptionsProvider;
}
[HttpGet]
public async Task<IActionResult> HandleAsync(string id, CancellationToken cancellationToken)
{
var label = await _store.FindByIdAsync(id, cancellationToken);
return label == null ? NotFound() : Json(label, _serializerOptionsProvider.CreateApiOptions());
}
}

View file

@ -0,0 +1,12 @@
namespace Elsa.Labels.Endpoints.Labels.Get;
/// <summary>
/// Provides policy names accepted by the <see cref="Get"/> endpoint.
/// </summary>
public static class Constants
{
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "GetLabel";
}

View file

@ -0,0 +1,33 @@
using Elsa.Labels.Services;
using FastEndpoints;
namespace Elsa.Labels.Endpoints.Labels.Get;
public class Get : Endpoint<Request, Response, LabelMapper>
{
private readonly ILabelStore _store;
public Get(ILabelStore store)
{
_store = store;
}
public override void Configure()
{
Get("/labels/{id}");
Policies(Constants.PolicyName);
}
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var label = await _store.FindByIdAsync(request.Id, cancellationToken);
if (label == null)
await SendNotFoundAsync(cancellationToken);
else
{
var response = await Map.FromEntityAsync(label);
await SendOkAsync(response, cancellationToken);
}
}
}

View file

@ -0,0 +1,16 @@
using Elsa.Labels.Entities;
using Elsa.Workflows.Core.Services;
using FastEndpoints;
namespace Elsa.Labels.Endpoints.Labels.Get;
public class LabelMapper : Mapper<Request, Response, Label>
{
public override Task<Response> FromEntityAsync(Label e) => Task.FromResult(new Response
{
Id = e.Id,
Color = e.Color,
Description = e.Description,
NormalizedName = e.NormalizedName
});
}

View file

@ -0,0 +1,14 @@
namespace Elsa.Labels.Endpoints.Labels.Get;
public class Request
{
public string Id { get; set; } = default!;
}
public class Response
{
public string Id { get; set; } = default!;
public string NormalizedName { get; set; } = default!;
public string? Description { get; set; }
public string? Color { get; set; }
}

View file

@ -1,31 +0,0 @@
using Elsa.AspNetCore.Attributes;
using Elsa.Labels.Entities;
using Elsa.Labels.Services;
using Elsa.Persistence.Common.Models;
using Elsa.Workflows.Core.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Elsa.Labels.Endpoints.Labels;
[Area(AreaNames.Elsa)]
[ApiEndpoint(ControllerNames.Labels, "List")]
[ProducesResponseType(typeof(Page<Label>), StatusCodes.Status200OK)]
public class List : Controller
{
private readonly ILabelStore _store;
private readonly SerializerOptionsProvider _serializerOptionsProvider;
public List(ILabelStore store, SerializerOptionsProvider serializerOptionsProvider)
{
_store = store;
_serializerOptionsProvider = serializerOptionsProvider;
}
[HttpGet]
public async Task<IActionResult> HandleAsync([FromQuery] PageArgs? pageArgs, CancellationToken cancellationToken = default)
{
var pageOfLabels = await _store.ListAsync(pageArgs, cancellationToken);
return Json(pageOfLabels, _serializerOptionsProvider.CreateApiOptions());
}
}

View file

@ -0,0 +1,14 @@
using Elsa.Labels.Endpoints.Labels.Get;
namespace Elsa.Labels.Endpoints.Labels.List;
/// <summary>
/// Provides policy names accepted by the <see cref="List"/> endpoint.
/// </summary>
public static class Constants
{
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "ListLabels";
}

View file

@ -0,0 +1,26 @@
using Elsa.Labels.Services;
using FastEndpoints;
namespace Elsa.Labels.Endpoints.Labels.List;
public class List : Endpoint<Request, Response, PageMapper>
{
private readonly ILabelStore _store;
public List(ILabelStore store)
{
_store = store;
}
public override void Configure()
{
Get("/labels");
Policies(Constants.PolicyName);
}
public override async Task<Response> ExecuteAsync(Request request, CancellationToken cancellationToken)
{
var pageOfLabels = await _store.ListAsync(request.ToPageArgs(), cancellationToken);
return Map.FromEntity(pageOfLabels);
}
}

View file

@ -0,0 +1,14 @@
using Elsa.Labels.Entities;
using Elsa.Models;
using FastEndpoints;
namespace Elsa.Labels.Endpoints.Labels.List;
public class PageMapper : Mapper<Request, Response, Page<Label>>
{
public override Response FromEntity(Page<Label> e) => new()
{
Items = e.Items,
TotalCount = e.TotalCount
};
}

View file

@ -0,0 +1,18 @@
using Elsa.Labels.Entities;
using Elsa.Models;
namespace Elsa.Labels.Endpoints.Labels.List;
public class Request
{
public int? Page { get; set; }
public int? PageSize { get; set; }
public PageArgs ToPageArgs() => new(Page, PageSize);
}
public class Response
{
public ICollection<Label> Items { get; set; } = default!;
public long TotalCount { get; set; }
}

View file

@ -0,0 +1,12 @@
namespace Elsa.Labels.Endpoints.Labels.Post;
/// <summary>
/// Provides policy names accepted by the <see cref="Post"/> endpoint.
/// </summary>
public static class Constants
{
/// <summary>
/// The policy name accepted by this endpoint.
/// </summary>
public const string PolicyName = "CreateLabel";
}

Some files were not shown because too many files have changed in this diff Show more