From dfe36b55a0ad6e02870f36c9544571795be03179 Mon Sep 17 00:00:00 2001 From: Behzad Alizadeh Date: Tue, 19 Aug 2025 18:26:51 +0330 Subject: [PATCH] Add Caspian.WorkflowFramework & Caspian.WorkflowFramework.Sample + Config these --- Directory.Packages.props | 348 ++++----- Elsa.sln | 55 +- .../Caspian.WorkflowFramework.Sample.csproj | 17 + .../Caspian.WorkflowFramework.Sample.http | 6 + .../Controllers/WorkflowController.cs | 76 ++ .../ExcludeExternalControllersFilter.cs | 28 + .../FodyWeavers.xml | 3 + .../Program.cs | 78 ++ .../Properties/launchSettings.json | 14 + .../appsettings.Development.json | 9 + .../appsettings.json | 322 ++++++++ .../appsettings.workflow.Development.json | 9 + .../appsettings.workflow.json | 285 ++++++++ .../Caspian.WorkflowFramework.csproj | 72 ++ .../Caspian.WorkflowFramework.nuspec | 130 ++++ .../Config/DependencyInjection.cs | 685 ++++++++++++++++++ .../Contracts/IWorkflowService.cs | 27 + .../Caspian.WorkflowFramework/FodyWeavers.xml | 3 + .../Caspian.WorkflowFramework/LICENSE.txt | 3 + .../Models/CreateAndExecuteWorkflowModel.cs | 31 + .../Models/InquiryStateModel.cs | 13 + .../Models/PublishEventModel.cs | 21 + .../Models/WorkflowModel.cs | 54 ++ .../Models/WorkflowResponse.cs | 35 + .../Services/WorkflowService.cs | 279 +++++++ .../Utility/ConversionExtensions.cs | 61 ++ .../Caspian.WorkflowFramework/favicon.ico | Bin 0 -> 1150 bytes .../gallery-icon.png | Bin 0 -> 4535 bytes .../nuget_command.txt | 1 + .../package-icon.png | Bin 0 -> 12783 bytes src/apps/Caspian.WorkflowFramework/readme.md | 35 + src/apps/Elsa.Server.Web/appsettings.json | 29 +- .../Elsa.ServerAndStudio.Web/appsettings.json | 27 + 33 files changed, 2562 insertions(+), 194 deletions(-) create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/Program.cs create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/appsettings.json create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json create mode 100644 src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json create mode 100644 src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj create mode 100644 src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec create mode 100644 src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs create mode 100644 src/apps/Caspian.WorkflowFramework/FodyWeavers.xml create mode 100644 src/apps/Caspian.WorkflowFramework/LICENSE.txt create mode 100644 src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs create mode 100644 src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs create mode 100644 src/apps/Caspian.WorkflowFramework/favicon.ico create mode 100644 src/apps/Caspian.WorkflowFramework/gallery-icon.png create mode 100644 src/apps/Caspian.WorkflowFramework/nuget_command.txt create mode 100644 src/apps/Caspian.WorkflowFramework/package-icon.png create mode 100644 src/apps/Caspian.WorkflowFramework/readme.md diff --git a/Directory.Packages.props b/Directory.Packages.props index 8b65fbf..03dedda 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,174 +1,178 @@ - - true - true - - - 3.5.0-preview.1092 - 9.0.7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + true + + + 3.5.0-preview.1092 + 9.0.7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Elsa.sln b/Elsa.sln index 29feedd..5baed45 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.34003.232 @@ -77,14 +76,14 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{986E5482-0482-448C-B9E4-EC67A9474B85}" ProjectSection(SolutionItems) = preProject docker\.dockerignore = docker\.dockerignore + docker\docker-compose-datadog+otel-collector.yml = docker\docker-compose-datadog+otel-collector.yml docker\docker-compose.yml = docker\docker-compose.yml docker\ElsaServer-Datadog.Dockerfile = docker\ElsaServer-Datadog.Dockerfile docker\ElsaServer.Dockerfile = docker\ElsaServer.Dockerfile docker\ElsaServerAndStudio.Dockerfile = docker\ElsaServerAndStudio.Dockerfile docker\ElsaStudio.Dockerfile = docker\ElsaStudio.Dockerfile - docker\otel-collector-config.yaml = docker\otel-collector-config.yaml docker\init-db-postgres.sh = docker\init-db-postgres.sh - docker\docker-compose-datadog+otel-collector.yml = docker\docker-compose-datadog+otel-collector.yml + docker\otel-collector-config.yaml = docker\otel-collector-config.yaml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Elasticsearch", "src\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj", "{3246883E-2FA7-4B4A-BDC5-99039A2869BC}" @@ -313,18 +312,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", scripts\migrations\efcore-3.1.sh = scripts\migrations\efcore-3.1.sh scripts\migrations\efcore-3.2.sh = scripts\migrations\efcore-3.2.sh scripts\migrations\efcore-3.3.sh = scripts\migrations\efcore-3.3.sh - scripts\migrations\README.md = scripts\migrations\README.md scripts\migrations\efcore-3.4.sh = scripts\migrations\efcore-3.4.sh scripts\migrations\efcore-3.5.sh = scripts\migrations\efcore-3.5.sh + scripts\migrations\README.md = scripts\migrations\README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3}" ProjectSection(SolutionItems) = preProject scripts\docker\build-and-run-all-in-one-web-docker.sh = scripts\docker\build-and-run-all-in-one-web-docker.sh - scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml - scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1 scripts\docker\docker-compose-citus.yml = scripts\docker\docker-compose-citus.yml scripts\docker\docker-compose-yugabyte.yml = scripts\docker\docker-compose-yugabyte.yml + scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml + scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-studio", "elsa-studio", "{DFFE6199-B5A7-4BA8-A5E1-77D0426C6468}" @@ -388,11 +387,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "adr", "adr", "{0A04B1FD-06C ProjectSection(SolutionItems) = preProject doc\adr\0001-record-architecture-decisions.md = doc\adr\0001-record-architecture-decisions.md doc\adr\0002-fault-propagation-from-child-to-parent-activities.md = doc\adr\0002-fault-propagation-from-child-to-parent-activities.md - doc\adr\toc.md = doc\adr\toc.md - doc\adr\graph.dot = doc\adr\graph.dot doc\adr\0003-direct-bookmark-management-in-workflowexecutioncontext.md = doc\adr\0003-direct-bookmark-management-in-workflowexecutioncontext.md doc\adr\0004-activity-execution-snapshots.md = doc\adr\0004-activity-execution-snapshots.md doc\adr\0005-tenant-deleted-event.md = doc\adr\0005-tenant-deleted-event.md + doc\adr\graph.dot = doc\adr\graph.dot + doc\adr\toc.md = doc\adr\toc.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bounty", "bounty", "{9B80A705-2E31-4012-964A-83963DCDB384}" @@ -416,6 +415,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.IO.Compression", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.IO.Http", "src\modules\Elsa.IO.Http\Elsa.IO.Http.csproj", "{C583AF05-D517-4B7F-8955-6B61500ED3D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Caspian.WorkflowFramework", "src\apps\Caspian.WorkflowFramework\Caspian.WorkflowFramework.csproj", "{A0F413B7-FA0F-472D-8BC2-6193DA440072}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Caspian.WorkflowFramework.Sample", "src\apps\Caspian.WorkflowFramework.Sample\Caspian.WorkflowFramework.Sample.csproj", "{C260D75F-19EB-4982-A98B-D8E126776910}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -709,14 +712,14 @@ Global {99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.ActiveCfg = Release|Any CPU {99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.Build.0 = Release|Any CPU {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.Build.0 = Release|Any CPU - {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.Build.0 = Release|Any CPU - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.Build.0 = Release|Any CPU + {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {832675FA-C597-4554-AE6B-18F189198A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {832675FA-C597-4554-AE6B-18F189198A1F}.Release|Any CPU.Build.0 = Release|Any CPU + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.Build.0 = Debug|Any CPU {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.ActiveCfg = Release|Any CPU {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.Build.0 = Release|Any CPU @@ -864,6 +867,14 @@ Global {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C583AF05-D517-4B7F-8955-6B61500ED3D8}.Release|Any CPU.Build.0 = Release|Any CPU + {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0F413B7-FA0F-472D-8BC2-6193DA440072}.Release|Any CPU.Build.0 = Release|Any CPU + {C260D75F-19EB-4982-A98B-D8E126776910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C260D75F-19EB-4982-A98B-D8E126776910}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C260D75F-19EB-4982-A98B-D8E126776910}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C260D75F-19EB-4982-A98B-D8E126776910}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -885,6 +896,7 @@ Global {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} {169E2C9B-6687-427F-A278-30BF849BFEDC} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} {2DD5D66B-85E9-4AF9-911C-C9F963234159} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} + {8A050229-DB79-4E0B-9AFF-7565E87F2954} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} {80529478-A383-4FEA-B744-C71264969E9A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} {39CD855E-83B1-4A96-93F7-01608211EBE3} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} @@ -958,10 +970,10 @@ Global {94A61AD7-2A2B-40DB-81F3-C59D596958A8} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} {4B598AF7-BD7D-4544-A274-2CDDD98F4167} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} {99B171E6-0248-4402-836D-98947CD63772} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {832675FA-C597-4554-AE6B-18F189198A1F} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} + {832675FA-C597-4554-AE6B-18F189198A1F} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} + {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} {97C7E531-9D5F-43FD-AA19-BF24DA13B612} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} {690B0274-291F-4D9E-BA76-54EFF7D3E4BC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} @@ -1007,7 +1019,6 @@ Global {FA2B2B44-7E3E-444B-A157-7526B376E9CA} = {9DF81143-2F39-4CFB-9DA3-902279010622} {DF0CA7E2-F5DF-40ED-91B6-748767084FC7} = {9DF81143-2F39-4CFB-9DA3-902279010622} {DB20B168-CD90-41F2-BE08-3D8C6F142459} = {9DF81143-2F39-4CFB-9DA3-902279010622} - {8A050229-DB79-4E0B-9AFF-7565E87F2954} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} {0A04B1FD-06C0-4271-A910-A08C263DBC44} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6} {9B80A705-2E31-4012-964A-83963DCDB384} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6} {CD7DC0D1-FFDC-417A-89BE-7F32408F583E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} @@ -1017,6 +1028,8 @@ Global {EB24F9FE-D7BD-4FCC-907E-AE400288C2A5} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77} {9CA02818-F7EB-4A0B-B27B-BC74ACD499C9} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77} {C583AF05-D517-4B7F-8955-6B61500ED3D8} = {7FD1FD1E-5778-4065-AAA5-1F878129EF77} + {A0F413B7-FA0F-472D-8BC2-6193DA440072} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {C260D75F-19EB-4982-A98B-D8E126776910} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj new file mode 100644 index 0000000..9bf6c98 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.csproj @@ -0,0 +1,17 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http new file mode 100644 index 0000000..c613358 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/Caspian.WorkflowFramework.Sample.http @@ -0,0 +1,6 @@ +@Caspian.WorkflowFramework.Sample_HostAddress = http://localhost:5008 + +GET {{Caspian.WorkflowFramework.Sample_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs b/src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs new file mode 100644 index 0000000..379e2f4 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/Controllers/WorkflowController.cs @@ -0,0 +1,76 @@ +using Caspian.WorkflowFramework; +using Caspian.WorkflowFramework.Contracts; +using Caspian.WorkflowFramework.Models; +using Elsa.Common.Models; +using Elsa.Workflows.Runtime.Matches; +using Microsoft.AspNetCore.Mvc; +using San.MongoLogger; +using Newtonsoft.Json; + +namespace Caspian.WorkflowFramework.Sample.Controllers; +public class WorkflowController : Controller, ILoggable +{ + #region Fields + private readonly IWorkflowService _workflowService; + private readonly IMdbLogger _logger; + #endregion + + #region Ctor + public WorkflowController(IWorkflowService workflowService, + IMdbLogger logger) + { + _workflowService = workflowService; + _logger = logger; + } + #endregion + + #region Start + [HttpPost("Start")] + public async Task Start(CreateAndExecuteWorkflowModel input) + { + _logger.Log("Start of StartWorkflow action", JsonConvert.SerializeObject(input)); + + //var model = _mapper.Map(input); + var result = await _workflowService.CreateAndExecuteAsync(new CreateAndExecuteWorkflowModel() + { + Name = input.Name, + UserId = input.UserId, + Version = VersionOptions.Published + }); + + _logger.Log("End of StartWorkflow action", WFInstanceId: result.Data?.InstanceId, JsonConvert.SerializeObject(result)); + + return Json(result); + } + #endregion + + #region Do + [HttpPost("Do")] + public async Task Do(PublishEventModel input) + { + _logger.Log("Start of Do action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input)); + + //var model = _mapper.Map(input); + var result = await _workflowService.PublishEventAsync(input); + + _logger.Log("End of Do action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result)); + + return Json(result); + } + #endregion + + #region GetState + [HttpPost("GetState")] + public async Task GetState(InquiryStateModel input) + { + _logger.Log("Start of GetState action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input)); + + //var model = _mapper.Map(input); + var result = await _workflowService.InquiryStateAsync(input); + + _logger.Log("End of GetState action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result)); + + return Json(result); + } + #endregion +} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs b/src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs new file mode 100644 index 0000000..540df78 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/ExcludeExternalControllersFilter.cs @@ -0,0 +1,28 @@ +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Caspian.WorkflowFramework.Sample; +public class ExcludeExternalControllersFilter : IDocumentFilter +{ + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + var currentAssembly = Assembly.GetExecutingAssembly(); + + var keysToRemove = context.ApiDescriptions + .Where(apiDesc => + { + var controllerType = apiDesc.ActionDescriptor?.EndpointMetadata?.OfType() + .FirstOrDefault()?.ControllerTypeInfo?.AsType(); + + return controllerType != null && controllerType.Assembly != currentAssembly; + }) + .Select(apiDesc => "/" + apiDesc.RelativePath.TrimEnd('/')) + .ToList(); + + foreach (var path in keysToRemove) + { + swaggerDoc.Paths.Remove(path); + } + } +} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml b/src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml new file mode 100644 index 0000000..00e1d9a --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Program.cs b/src/apps/Caspian.WorkflowFramework.Sample/Program.cs new file mode 100644 index 0000000..16d0e68 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/Program.cs @@ -0,0 +1,78 @@ +using Caspian.WorkflowFramework; +using Caspian.WorkflowFramework.Sample; +using Microsoft.OpenApi.Models; +using San.MongoLogger; +using San.MongoLogger.Models; +using Parlot.Fluent; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowSpecificOrigin", + builder => builder.AllowAnyOrigin() // Add your client origin + .AllowAnyMethod() // Allow any HTTP method + .AllowAnyHeader()); // Allow any header +}); + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +//builder.Services.AddOpenApi(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); + c.DocumentFilter(); + c.CustomSchemaIds(type => type.FullName); +}); + +//Workflow +//var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); +var t = $"appsettings.workflow.{builder.Environment.EnvironmentName}.json"; +builder.AddWorkflowFramework($"appsettings.workflow.json"); + +builder.Services.AddMongoLogger(builder.Configuration); + +var app = builder.Build(); + +app.UseMongoLogger(); + +// Enable CORS before authentication and authorization +app.UseCors("AllowSpecificOrigin"); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + //app.MapOpenApi(); + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root URL + }); +} + +app.UseAuthorization(); + +app.MapControllers(); + +//Workflow +app.UseWorkflowFramework(); + +// Redirect to Swagger URL +app.Use(async (context, next) => +{ + // Check if current request is the root URL ("/") + if (context.Request.Path == "/") + { + // Redirect to Swagger UI + context.Response.Redirect("/swagger/index.html"); + return; + } + + await next(); // Proceed to the next middleware if not redirecting +}); + +app.Run(); diff --git a/src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json b/src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json new file mode 100644 index 0000000..afcfdbe --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5008", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json new file mode 100644 index 0000000..84dfb83 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + //"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging + } + } +} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.json new file mode 100644 index 0000000..eab431b --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.json @@ -0,0 +1,322 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + //"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging + } + }, + "LogDatabaseSettings": { + "ConnectionString": "mongodb://localhost", + "DatabaseName": "Caspian_WorkflowFramework_Sample_DB", + "LogsCollectionName": "Col", + "BatchSize": 0, + "RollingInterval": "Daily", + "MaskList": [ + "code", + "depositNumber", + "username", + "customerNo", + "password", + "sessionId", + "mainAccount", + "accountNumber", + "owners", + "balance", + "accountNo", + "depositUniqueId", + "externalRefList", + "externalref", + "sameSignatureDepositNumber", + "mobileNumber", + "destinationNumber", + "cardNumber" + ] + }, + "AllowedHosts": "*", + "HostBuilder": { + "reloadConfigOnChange": true + }, + "ConnectionStrings": { + "DefaultConnection": "Data Source=.;Initial Catalog=WorkflowFrameworkTest;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;", + //"Sqlite": "", //"Data Source=App_Data/elsa.sqlite.db;Cache=Shared;", + ////"SqlServer": "Server=localhost,1433;Initial Catalog=Elsa;User=sa;Password=!Elsa2025@;Encrypt=false;PersistSecurityInfo=false", + //"SqlServer": "Data Source=.;Initial Catalog=WorkflowFrameworkTest;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;", + //"MySql": "", //"Server=localhost;Database=elsa;Uid=admin;Pwd=password;", + //"PostgreSql": "", //"Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60", + //"Citus": "", //"Server=localhost;Username=citus;Database=citus;Port=9700;Password=citus;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60", + //"YugabyteDb": "", //"Host=localhost;Port=5433;Database=elsa;Username=yugabyte;Password=yugabyte;Pooling=true", + //"Oracle": "", //"Data Source=(DESCRIPTION = (ADDRESS_LIST = (FAILOVER =ON) (LOAD_BALANCE = OFF) (ADDRESS = (PROTOCOL =TCP)(HOST=localhost)(PORT=1521))) (CONNECT_DATA = (SID= FREE) ));User Id=ELSA;Password=elsa;", + //"CockroachDb": "", //"Host=localhost;Port=26257;Database=elsa;SslMode=Disable;Username=root;IncludeErrorDetail=true", + //"MongoDb": "", //"mongodb://localhost:27017/elsa-workflows", + //"AzureServiceBus": "", + //"RabbitMq": "", //"amqp://guest:guest@localhost:5672", + //"Redis": "" //"localhost:6379,abortConnect=false" + } + //"DatabaseProvider": "SqlServer", //"Sqlite", + //"Multitenancy": { + // "Tenants": [ + // { + // "Id": null, + // "Name": "Default", + // "Configuration": { + // "Http": { + // "Prefix": "", + // "Host": "localhost:5001" + // }, + // "ConnectionStrings": { + // "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;" + // } + // } + // }, + // { + // "Id": "tenant-1", + // "Name": "Tenant 1", + // "Configuration": { + // "Http": { + // "Prefix": "tenant-1", + // "Host": "localhost:5001" + // }, + // "ConnectionStrings": { + // "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;" + // } + // } + // } + // ] + //}, + //"Smtp": { + // "Host": "localhost", + // "Port": 2525, + // "DefaultSender": "noreply@crmservices.com" + //}, + //"Http": { + // "BaseUrl": "https://localhost:5001", + // "BasePath": "/workflows", + // "ApiRoutePrefix": "elsa/api", + // "AvailableContentTypes": [ + // "application/json", + // "application/xml", + // "text/plain", + // "text/html" + // ] + //}, + //"Identity": { + // "Tokens": { + // "SigningKey": "sufficiently-large-secret-signing-key", + // "AccessTokenLifetime": "1:00:00:00", + // "RefreshTokenLifetime": "7:00:00:00" + // }, + // "Roles": [ + // { + // "Id": "admin", + // "Name": "Administrator", + // "Permissions": [ + // "*" + // ], + // "TenantId": "default" + // } + // ], + // "Users": [ + // { + // "Id": "a2323f46-42db-4e15-af8b-94238717d817", + // "Name": "admin", + // //"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=", + // //"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=", + // "HashedPassword": "p90gXKnVpt5Q2VZ4KkeOaM5GZN3bqjczoznWbnFMrvo=", + // "HashedPasswordSalt": "QmVzbWVsbGFoQHJSYWhtYW5AcnJhaDFt", + // "Roles": [ + // "admin" + // ], + // "TenantId": "" + // }, + // { + // "Id": "b0cd0e506e713a9d", + // "Name": "alice", + // "Roles": [ + // "admin" + // ], + // "HashedPassword": "8B0fFK/f/kk9GkVtzXfRJ2Y6cNyYVvLTfKouWcAcuPg=", + // "HashedPasswordSalt": "xlNWvEng8fRvo0McyJopbRJ2MJ9NIYV/4IY5dOZeiiw=", + // "TenantId": "tenant-2" + // }, + // { + // "Id": "789d5056cb3bb8e9", + // "Name": "bob", + // "Roles": [ + // "admin" + // ], + // "HashedPassword": "GMHHvUw7RfIIBq+GAWZ/AlHR50TbgONYfGQ92Xge0EI=", + // "HashedPasswordSalt": "V4uHKg1PFBc1bsngCQoptibOu14faj9obSn5B23h1wE=", + // "TenantId": "tenant-1" + // } + // ], + // "Applications": [ + // { + // "Id": "d57030226341448daff5a2935aba2d3f", + // "Name": "Postman", + // "Roles": [ + // "admin" + // ], + // "ClientId": "HXr0Vzdm9KCZbwsJ", + // "ClientSecret": "a<~QGGHTEA%u4;CU&'Wga5ED:_&Gd1C)", + // "HashedApiKey": "Z5ClHs3mbzx8Pnw3+PxbMq8A/Y+VKMCCDTGYtax8JFM=", + // "HashedApiKeySalt": "kBisa1X8FwBfN2zmyGMFRgIVVBleghhQAJ4WGyTkaD0=", + // "HashedClientSecret": "jEv58d0SVbGQ3nBZM0lkzHghG4Y+lMKW80wipz+9vHk=", + // "HashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU=", + // "TenantId": "default" + // } + // ] + //}, + //"Environments": { + // "Environments": [ + // { + // "Name": "Local", + // "ServerUrl": "https://localhost:5001/elsa/api" + // }, + // { + // "Name": "Staging", + // "ServerUrl": "https://staging.acme.com/elsa/api" + // }, + // { + // "Name": "Production", + // "ServerUrl": "https://production.acme.com/elsa/api" + // } + // ] + //}, + //"AppRole": "Default", + //"Runtime": { + // "DistributedLocking": { + // "Provider": "File", + // "LockAcquisitionTimeout": "00:00:10" + // }, + // "DistributedLockProvider": "File", + // "WorkflowDispatcher": { + // "Channels": [ + // { + // "Name": "Low" + // }, + // { + // "Name": "Medium" + // }, + // { + // "Name": "High" + // } + // ] + // } + //}, + //"Scripting": { + // "Python": { + // "PythonDllPath": "", + // "Scripts": [ + // "def greet(name): return f'Hello {name}!'", + // "def say_hello_world(): return greet('World')" + // ] + // } + //}, + //"AzureServiceBus": { + // "Queues": [ + // { + // "Name": "queue1" + // }, + // { + // "Name": "queue2" + // } + // ], + // "Topics": [ + // { + // "Name": "topic1", + // "Subscriptions": [ + // { + // "Name": "subscription1" + // }, + // { + // "Name": "subscription2" + // } + // ] + // } + // ] + //}, + //"Kafka": { + // "Topics": [ + // { + // "Id": "topic-1", + // "Name": "topic-1" + // }, + // { + // "Id": "topic-2", + // "Name": "topic-2" + // } + // ], + // "SchemaRegistries": [ + // { + // "Id": "schema-registry-1", + // "Name": "Schema Registry 1", + // "Config": { + // "Url": "http://localhost:8081" + // } + // } + // ], + // "Producers": [ + // { + // "Id": "producer-1", + // "Name": "Producer 1", + // "FactoryType": "Elsa.Kafka.Factories.GenericProducerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka", + // "Config": { + // "BootstrapServers": "localhost:9092" + // }, + // "SchemaRegistryId": "schema-registry-1" + // } + // ], + // "Consumers": [ + // { + // "Id": "consumer-1", + // "Name": "Consumer 1", + // "FactoryType": "Elsa.Kafka.Factories.GenericConsumerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka", + // "Config": { + // "BootstrapServers": "localhost:9092", + // "GroupId": "group-1", + // "AutoOffsetReset": "earliest", + // "EnableAutoCommit": "true" + // }, + // "SchemaRegistryId": "schema-registry-1" + // } + // ] + //}, + //"Secrets": { + // "Management": { + // "SweepInterval": "04:00:00" + // } + //}, + //"Webhooks": { + // "Sinks": [ + // { + // "Id": "1", + // "Name": "Run Task", + // "Filters": [ + // { + // "EventType": "Elsa.RunTask" + // } + // ], + // "Url": "https://localhost:6001/webhooks/run-task" + // } + // ] + //}, + //"Resilience": { + // "Strategies": [ + // { + // "$type": "HttpResilienceStrategy", + // "Id": "1", + // "DisplayName": "Retry 3 times, exponential", + // "MaxRetryAttempts": 3, + // "BackoffType": "Exponential" + // }, + // { + // "$type": "HttpResilienceStrategy", + // "Id": "2", + // "DisplayName": "Retry 10 times, linear", + // "MaxRetryAttempts": 10, + // "BackoffType": "Linear" + // } + // ] + //} +} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json new file mode 100644 index 0000000..84dfb83 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + //"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging + } + } +} diff --git a/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json new file mode 100644 index 0000000..5ab381e --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework.Sample/appsettings.workflow.json @@ -0,0 +1,285 @@ +{ + "HostBuilder": { + "reloadConfigOnChange": true + }, + "ConnectionStrings": { + //"Sqlite": "", //"Data Source=App_Data/elsa.sqlite.db;Cache=Shared;", + "SqlServer": "Data Source=.;Initial Catalog=WorkflowDb_3-5-0;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;" + //"MySql": "", //"Server=localhost;Database=elsa;Uid=admin;Pwd=password;", + //"PostgreSql": "", //"Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60", + //"Citus": "", //"Server=localhost;Username=citus;Database=citus;Port=9700;Password=citus;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60", + //"YugabyteDb": "", //"Host=localhost;Port=5433;Database=elsa;Username=yugabyte;Password=yugabyte;Pooling=true", + //"Oracle": "", //"Data Source=(DESCRIPTION = (ADDRESS_LIST = (FAILOVER =ON) (LOAD_BALANCE = OFF) (ADDRESS = (PROTOCOL =TCP)(HOST=localhost)(PORT=1521))) (CONNECT_DATA = (SID= FREE) ));User Id=ELSA;Password=elsa;", + //"CockroachDb": "", //"Host=localhost;Port=26257;Database=elsa;SslMode=Disable;Username=root;IncludeErrorDetail=true", + //"MongoDb": "", //"mongodb://localhost:27017/elsa-workflows", + //"AzureServiceBus": "", + //"RabbitMq": "", //"amqp://guest:guest@localhost:5672", + //"Redis": "" //"localhost:6379,abortConnect=false" + }, + "DatabaseProvider": "SqlServer", //"Sqlite", + "Multitenancy": { + "Tenants": [ + { + "Id": null, + "Name": "Default", + "Configuration": { + "Http": { + "Prefix": "", + "Host": "localhost:5001" + }, + "ConnectionStrings": { + "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;" + } + } + }, + { + "Id": "tenant-1", + "Name": "Tenant 1", + "Configuration": { + "Http": { + "Prefix": "tenant-1", + "Host": "localhost:5001" + }, + "ConnectionStrings": { + "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;" + } + } + } + ] + }, + "Smtp": { + "Host": "localhost", + "Port": 2525, + "DefaultSender": "noreply@crmservices.com" + }, + "Http": { + "BaseUrl": "https://localhost:5001", + "BasePath": "/workflows", + "ApiRoutePrefix": "elsa/api", + "AvailableContentTypes": [ + "application/json", + "application/xml", + "text/plain", + "text/html" + ] + }, + "Identity": { + "Tokens": { + "SigningKey": "sufficiently-large-secret-signing-key", + "AccessTokenLifetime": "1:00:00:00", + "RefreshTokenLifetime": "7:00:00:00" + }, + "Roles": [ + { + "Id": "admin", + "Name": "Administrator", + "Permissions": [ + "*" + ], + "TenantId": "default" + } + ], + "Users": [ + { + "Id": "a2323f46-42db-4e15-af8b-94238717d817", + "Name": "admin", + //"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=", + //"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=", + "HashedPassword": "p90gXKnVpt5Q2VZ4KkeOaM5GZN3bqjczoznWbnFMrvo=", + "HashedPasswordSalt": "QmVzbWVsbGFoQHJSYWhtYW5AcnJhaDFt", + "Roles": [ + "admin" + ], + "TenantId": "" + }, + { + "Id": "b0cd0e506e713a9d", + "Name": "alice", + "Roles": [ + "admin" + ], + "HashedPassword": "8B0fFK/f/kk9GkVtzXfRJ2Y6cNyYVvLTfKouWcAcuPg=", + "HashedPasswordSalt": "xlNWvEng8fRvo0McyJopbRJ2MJ9NIYV/4IY5dOZeiiw=", + "TenantId": "tenant-2" + }, + { + "Id": "789d5056cb3bb8e9", + "Name": "bob", + "Roles": [ + "admin" + ], + "HashedPassword": "GMHHvUw7RfIIBq+GAWZ/AlHR50TbgONYfGQ92Xge0EI=", + "HashedPasswordSalt": "V4uHKg1PFBc1bsngCQoptibOu14faj9obSn5B23h1wE=", + "TenantId": "tenant-1" + } + ], + "Applications": [ + { + "Id": "d57030226341448daff5a2935aba2d3f", + "Name": "Postman", + "Roles": [ + "admin" + ], + "ClientId": "HXr0Vzdm9KCZbwsJ", + "ClientSecret": "a<~QGGHTEA%u4;CU&'Wga5ED:_&Gd1C)", + "HashedApiKey": "Z5ClHs3mbzx8Pnw3+PxbMq8A/Y+VKMCCDTGYtax8JFM=", + "HashedApiKeySalt": "kBisa1X8FwBfN2zmyGMFRgIVVBleghhQAJ4WGyTkaD0=", + "HashedClientSecret": "jEv58d0SVbGQ3nBZM0lkzHghG4Y+lMKW80wipz+9vHk=", + "HashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU=", + "TenantId": "default" + } + ] + }, + "Environments": { + "Environments": [ + { + "Name": "Local", + "ServerUrl": "https://localhost:5001/elsa/api" + }, + { + "Name": "Staging", + "ServerUrl": "https://staging.acme.com/elsa/api" + }, + { + "Name": "Production", + "ServerUrl": "https://production.acme.com/elsa/api" + } + ] + }, + "AppRole": "Default", + "Runtime": { + "DistributedLocking": { + "Provider": "File", + "LockAcquisitionTimeout": "00:00:10" + }, + "DistributedLockProvider": "File", + "WorkflowDispatcher": { + "Channels": [ + { + "Name": "Low" + }, + { + "Name": "Medium" + }, + { + "Name": "High" + } + ] + } + }, + "Scripting": { + "Python": { + "PythonDllPath": "", + "Scripts": [ + "def greet(name): return f'Hello {name}!'", + "def say_hello_world(): return greet('World')" + ] + } + }, + "AzureServiceBus": { + "Queues": [ + { + "Name": "queue1" + }, + { + "Name": "queue2" + } + ], + "Topics": [ + { + "Name": "topic1", + "Subscriptions": [ + { + "Name": "subscription1" + }, + { + "Name": "subscription2" + } + ] + } + ] + }, + "Kafka": { + "Topics": [ + { + "Id": "topic-1", + "Name": "topic-1" + }, + { + "Id": "topic-2", + "Name": "topic-2" + } + ], + "SchemaRegistries": [ + { + "Id": "schema-registry-1", + "Name": "Schema Registry 1", + "Config": { + "Url": "http://localhost:8081" + } + } + ], + "Producers": [ + { + "Id": "producer-1", + "Name": "Producer 1", + "FactoryType": "Elsa.Kafka.Factories.GenericProducerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka", + "Config": { + "BootstrapServers": "localhost:9092" + }, + "SchemaRegistryId": "schema-registry-1" + } + ], + "Consumers": [ + { + "Id": "consumer-1", + "Name": "Consumer 1", + "FactoryType": "Elsa.Kafka.Factories.GenericConsumerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka", + "Config": { + "BootstrapServers": "localhost:9092", + "GroupId": "group-1", + "AutoOffsetReset": "earliest", + "EnableAutoCommit": "true" + }, + "SchemaRegistryId": "schema-registry-1" + } + ] + }, + "Secrets": { + "Management": { + "SweepInterval": "04:00:00" + } + }, + "Webhooks": { + "Sinks": [ + { + "Id": "1", + "Name": "Run Task", + "Filters": [ + { + "EventType": "Elsa.RunTask" + } + ], + "Url": "https://localhost:6001/webhooks/run-task" + } + ] + }, + "Resilience": { + "Strategies": [ + { + "$type": "HttpResilienceStrategy", + "Id": "1", + "DisplayName": "Retry 3 times, exponential", + "MaxRetryAttempts": 3, + "BackoffType": "Exponential" + }, + { + "$type": "HttpResilienceStrategy", + "Id": "2", + "DisplayName": "Retry 10 times, linear", + "MaxRetryAttempts": 10, + "BackoffType": "Linear" + } + ] + } +} diff --git a/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj new file mode 100644 index 0000000..d9a20dd --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.csproj @@ -0,0 +1,72 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec new file mode 100644 index 0000000..0c750ba --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Caspian.WorkflowFramework.nuspec @@ -0,0 +1,130 @@ + + + + Caspian.WorkflowFramework + 1.0.0-preview34 + Digital Banking Team + Caspian + Caspian workflow framework + https://caspco.ir + package-icon.png + readme.md + caspian, nextbank, workflow + LICENSE.txt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs b/src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs new file mode 100644 index 0000000..1c0dbb4 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Config/DependencyInjection.cs @@ -0,0 +1,685 @@ +using System.IO; +using System.Text.Encodings.Web; +using Caspian.WorkflowFramework.Contracts; +using Caspian.WorkflowFramework.Services; +using Elsa.Alterations.Extensions; +using Elsa.Alterations.MassTransit.Extensions; +using Elsa.Caching.Options; +using Elsa.Common.Codecs; +using Elsa.Common.DistributedHosting.DistributedLocks; +using Elsa.Common.RecurringTasks; +using Elsa.Common.Serialization; +using Elsa.Dapper.Extensions; +using Elsa.Dapper.Services; +using Elsa.DropIns.Extensions; +using Elsa.EntityFrameworkCore.Extensions; +using Elsa.EntityFrameworkCore.Modules.Alterations; +using Elsa.EntityFrameworkCore.Modules.Identity; +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Elsa.Expressions.Helpers; +using Elsa.Extensions; +using Elsa.Features.Services; +using Elsa.Identity.Multitenancy; +using Elsa.Kafka; +using Elsa.Kafka.Factories; +using Elsa.MassTransit.Extensions; +using Elsa.MongoDb.Extensions; +using Elsa.MongoDb.Modules.Alterations; +using Elsa.MongoDb.Modules.Identity; +using Elsa.MongoDb.Modules.Management; +using Elsa.MongoDb.Modules.Runtime; +using Elsa.MongoDb.Modules.Tenants; +using Elsa.OpenTelemetry.Middleware; +using Elsa.Retention.Extensions; +using Elsa.Retention.Models; +using Elsa.Secrets.Extensions; +using Elsa.Secrets.Management.Tasks; +using Elsa.Secrets.Persistence; +using Elsa.Server.Web; +using Elsa.Server.Web.Extensions; +using Elsa.Server.Web.Filters; +using Elsa.Server.Web.Messages; +using Elsa.Sql.Extensions; +using Elsa.Sql.MySql; +using Elsa.Sql.PostgreSql; +using Elsa.Sql.Sqlite; +using Elsa.Sql.SqlServer; +using Elsa.Tenants.AspNetCore; +using Elsa.Tenants.Extensions; +using Elsa.Workflows; +using Elsa.Workflows.Api; +using Elsa.Workflows.CommitStates.Strategies; +using Elsa.Workflows.IncidentStrategies; +using Elsa.Workflows.LogPersistence; +using Elsa.Workflows.Management; +using Elsa.Workflows.Management.Stores; +using Elsa.Workflows.Options; +using Elsa.Workflows.Runtime.Distributed.Extensions; +using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Stores; +using Elsa.Workflows.Runtime.Tasks; +using Hangfire; +using Hangfire.MemoryStorage; +using Hangfire.PostgreSql; +using Hangfire.PostgreSql.Factories; +using Hangfire.SqlServer; +using Hangfire.Storage.SQLite; +using JetBrains.Annotations; +using Medallion.Threading.FileSystem; +using Medallion.Threading.Postgres; +using Medallion.Threading.Redis; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Proto.Cluster.Kubernetes; +using Proto.Persistence.Sqlite; +using Proto.Persistence.SqlServer; +using Proto.Remote; +using Proto.Remote.GrpcNet; +using San.MongoLogger; +using San.MongoLogger.Models; +using StackExchange.Redis; + +namespace Caspian.WorkflowFramework; + +public static class DependencyInjection +{ + const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore; + const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed; + const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.Memory; + const MassTransitBroker massTransitBroker = MassTransitBroker.Memory; + const bool useDbContextPooling = false; + const bool useHangfire = false; + const bool useQuartz = true; + const bool useMassTransit = false; + const bool useZipCompression = false; + const bool runEFCoreMigrations = true; + const bool useMemoryStores = false; + const bool useCaching = true; + const bool useKafka = false; + const bool useReadOnlyMode = false; + const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests. + const bool useMultitenancy = false; + const bool useTenantsFromConfiguration = true; + const bool useSecrets = false; + const bool disableVariableWrappers = false; + const bool disableVariableCopying = false; + const bool useManualOtelInstrumentation = false; + + public static void AddWorkflowFramework(this IHostApplicationBuilder builder, string appsettingName) + { + var configuration = new ConfigurationBuilder() + .AddJsonFile(appsettingName, optional: false, reloadOnChange: true) + .Build(); + //var configuration = builder.Configuration; + var heartbeatSection = configuration.GetSection("Heartbeat"); + var identitySection = configuration.GetSection("Identity"); + var identityTokenSection = identitySection.GetSection("Tokens"); + var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!; + var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!; + var postgresConnectionString = configuration.GetConnectionString("PostgreSql")!; + var citusConnectionString = configuration.GetConnectionString("Citus")!; + var yugabyteDbConnectionString = configuration.GetConnectionString("YugabyteDb")!; + var oracleConnectionString = configuration.GetConnectionString("Oracle")!; + var mySqlConnectionString = configuration.GetConnectionString("MySql")!; + var cockroachDbConnectionString = configuration.GetConnectionString("CockroachDb")!; + var mongoDbConnectionString = configuration.GetConnectionString("MongoDb")!; + var azureServiceBusConnectionString = configuration.GetConnectionString("AzureServiceBus")!; + var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!; + var redisConnectionString = configuration.GetConnectionString("Redis")!; + var distributedLockProviderName = configuration.GetSection("Runtime:DistributedLocking")["Provider"]; + var appRole = Enum.Parse(configuration["AppRole"]! ?? "Default"); + var sqlDatabaseProvider = Enum.Parse(configuration["DatabaseProvider"] ?? "SqlServer"); + const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore; + const bool useDbContextPooling = false; + const bool useHangfire = false; + const bool useQuartz = false;//true; + const bool useMassTransit = false; + const bool useZipCompression = false; + const bool runEFCoreMigrations = true; + const bool useMemoryStores = false; + const bool useCaching = true; + const bool useKafka = false; + const bool useReadOnlyMode = false; + const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests. + const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed; + const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.Memory; + const MassTransitBroker massTransitBroker = MassTransitBroker.Memory; + const bool useMultitenancy = false; + const bool useTenantsFromConfiguration = false;// true; + const bool useSecrets = false; + const bool disableVariableWrappers = false; + const bool disableVariableCopying = false; + const bool useManualOtelInstrumentation = false; + + // Optionally create type aliases for easier configuration. + TypeAliasRegistry.RegisterAlias("OrderReceivedProducerFactory", typeof(GenericProducerFactory)); + TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory)); + + if (useManualOtelInstrumentation) + { + builder.Services + .AddOpenTelemetry() + .ConfigureResource(resource => resource.AddService("elsa-workflows", serviceVersion: "3.5.0").AddTelemetrySdk()) + .WithTracing(tracing => + { + tracing + .AddSource("*") + .SetSampler(new AlwaysOnSampler()) + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddSqlClientInstrumentation() + .AddConsoleExporter() + .AddOtlpExporter() + ; + }) + .WithMetrics(metrics => + { + metrics + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddConsoleExporter() + .AddOtlpExporter() + ; + }); + + // Enable OpenTelemetry Logging (optional) + builder.Logging.AddOpenTelemetry(options => + { + options.IncludeFormattedMessage = true; + options.IncludeScopes = true; + options.ParseStateValues = true; + }); + } + + // Add Elsa services. + builder.Services.AddScoped(); + + builder.Services.AddMongoLogger(builder.Configuration); + + builder.Services.AddElsa(elsa => + { + elsa + .UseWorkflows(workflows => + { + workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing()); + workflows.WithDefaultActivityExecutionPipeline(pipeline => pipeline.UseActivityExecutionTracing()); + workflows.UseCommitStrategies(strategies => + { + strategies.AddStandardStrategies(); + strategies.Add("Every 10 seconds", new PeriodicWorkflowStrategy(TimeSpan.FromSeconds(10))); + }); + }) + .UseWorkflowManagement(management => + { + management.UseEntityFrameworkCore(ef => + { + ef.UseSqlServer(sqlServerConnectionString); + }); + + if (useZipCompression) + management.SetCompressionAlgorithm(nameof(Zstd)); + + if (useMemoryStores) + management.UseWorkflowInstances(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService()); + + if (useMassTransit) + management.UseMassTransitDispatcher(); + + if (useCaching) + management.UseCache(); + + management.SetDefaultLogPersistenceMode(LogPersistenceMode.Inherit); + management.UseReadOnlyMode(useReadOnlyMode); + management.AddVariableTypeAndAlias("Application"); + }) + .UseWorkflowRuntime(runtime => + { + runtime.UseEntityFrameworkCore(ef => + { + ef.UseContextPooling = useDbContextPooling; + + //ef.UseSqlServer(sqlServerConnectionString, new ElsaDbContextOptions); + var migrationsAssembly = typeof(Elsa.EntityFrameworkCore.SqlServer.IdentityDbContextFactory).Assembly; + var connectionString = sqlServerConnectionString; + ef.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(migrationsAssembly, connectionString, null, configure => configure.CommandTimeout(60000)); + + ef.RunMigrations = runEFCoreMigrations; + }); + + if (workflowRuntime == WorkflowRuntime.Distributed) + { + runtime.UseDistributedRuntime(); + } + + if (workflowRuntime == WorkflowRuntime.ProtoActor) + { + runtime.UseProtoActor(); + } + + if (useMassTransit) + runtime.UseMassTransitDispatcher(); + + runtime.WorkflowDispatcherOptions = options => configuration.GetSection("Runtime:WorkflowDispatcher").Bind(options); + + if (useMemoryStores) + { + runtime.ActivityExecutionLogStore = sp => sp.GetRequiredService(); + runtime.WorkflowExecutionLogStore = sp => sp.GetRequiredService(); + } + + if (useCaching) + runtime.UseCache(); + + runtime.DistributedLockingOptions = options => configuration.GetSection("Runtime:DistributedLocking").Bind(options); + + runtime.DistributedLockProvider = _ => + { + switch (distributedLockProviderName) + { + case "Postgres": + return new PostgresDistributedSynchronizationProvider(postgresConnectionString, options => + { + options.KeepaliveCadence(TimeSpan.FromMinutes(5)); + options.UseMultiplexing(); + }); + case "Redis": + { + var connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnectionString); + var database = connectionMultiplexer.GetDatabase(); + return new RedisDistributedSynchronizationProvider(database); + } + case "File": + return new FileDistributedSynchronizationProvider(new(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "locks"))); + case "Noop": + default: + return new NoopDistributedSynchronizationProvider(); + } + }; + }) + .UseEnvironments(environments => environments.EnvironmentsOptions = options => configuration.GetSection("Environments").Bind(options)) + .UseScheduling(scheduling => + { + if (useHangfire) + scheduling.UseHangfireScheduler(); + + if (useQuartz) + scheduling.UseQuartzScheduler(); + }) + .UseWorkflowsApi(api => + { + api.AddFastEndpointsAssembly(); + }) + .UseCSharp(options => + { + options.DisableWrappers = disableVariableWrappers; + options.AppendScript("string Greet(string name) => $\"Hello {name}!\";"); + options.AppendScript("string SayHelloWorld() => Greet(\"World\");"); + }) + .UseJavaScript(options => + { + options.AllowClrAccess = true; + options.DisableWrappers = disableVariableWrappers; + options.DisableVariableCopying = disableVariableCopying; + options.RegisterType(); + options.ConfigureEngine(engine => + { + engine.Execute("function greet(name) { return `Hello ${name}!`; }"); + engine.Execute("function sayHelloWorld() { return greet('World'); }"); + }); + }) + .UsePython(python => + { + python.PythonOptions += options => + { + // Make sure to configure the path to the python DLL. E.g. /opt/homebrew/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/bin/python3.11 + // alternatively, you can set the PYTHONNET_PYDLL environment variable. + configuration.GetSection("Scripting:Python").Bind(options); + + options.AddScript(sb => + { + sb.AppendLine("def greet():"); + sb.AppendLine(" return \"Hello, welcome to Python!\""); + }); + }; + }) + .UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default) + .UseHttp(http => + { + http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options); + + if (useCaching) + http.UseCache(); + }) + .UseSql(options => + { + options.Clients = client => + { + client.Register("MySql"); + client.Register("PostgreSql"); + client.Register("Sqlite"); + client.Register("Sql Server"); + }; + }) + .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)) + .UseAlterations(alterations => + { + if (persistenceProvider == PersistenceProvider.MongoDb) + { + alterations.UseMongoDb(); + } + else if (persistenceProvider == PersistenceProvider.Dapper) + { + // TODO: alterations.UseDapper(); + } + else + { + alterations.UseEntityFrameworkCore(ef => + { + ef.UseContextPooling = useDbContextPooling; + ef.UseSqlServer(sqlServerConnectionString); + ef.RunMigrations = runEFCoreMigrations; + }); + } + + if (useMassTransit) + { + alterations.UseMassTransitDispatcher(); + } + }) + .UseOpenTelemetry(otel => otel.UseNewRootActivityForRemoteParent = true) + .UseWorkflowContexts(); + + if (useQuartz) + { + elsa.UseQuartz(quartz => + { + if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) + quartz.UseSqlite(sqliteConnectionString); + else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) + quartz.UsePostgreSql(postgresConnectionString); + }); + } + + if (useSignalR) + { + elsa.UseRealTimeWorkflows(); + } + + if (useMassTransit) + { + elsa.UseMassTransit(massTransit => + { + massTransit.DisableConsumers = appRole == ApplicationRole.Api; + massTransit.AddMessageType(); + + if (massTransitBroker == MassTransitBroker.AzureServiceBus) + { + massTransit.UseAzureServiceBus(azureServiceBusConnectionString, serviceBusFeature => serviceBusFeature.ConfigureTransportBus = (context, bus) => + { + bus.PrefetchCount = 50; + bus.LockDuration = TimeSpan.FromMinutes(5); + bus.MaxConcurrentCalls = 32; + bus.MaxDeliveryCount = 8; + // etc. + }); + } + + if (massTransitBroker == MassTransitBroker.RabbitMq) + { + massTransit.UseRabbitMq(rabbitMqConnectionString, rabbit => rabbit.ConfigureTransportBus = (context, bus) => + { + bus.PrefetchCount = 50; + bus.Durable = true; + bus.AutoDelete = false; + bus.ConcurrentMessageLimit = 32; + // etc. + }); + } + }); + } + + if (distributedCachingTransport != DistributedCachingTransport.None) + { + elsa.UseDistributedCache(distributedCaching => + { + if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit(); + if (distributedCachingTransport == DistributedCachingTransport.ProtoActor) distributedCaching.UseProtoActor(); + }); + } + + if (distributedCachingTransport == DistributedCachingTransport.ProtoActor || workflowRuntime == WorkflowRuntime.ProtoActor) + { + elsa.UseProtoActor(proto => + { + proto + .EnableMetrics() + .EnableTracing(); + + proto.PersistenceProvider = _ => + { + if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) + return new SqlServerProvider(sqlServerConnectionString, true, "", "proto_actor"); + return new SqliteProvider(new(sqliteConnectionString)); + }; + + if (configuration["KUBERNETES_SERVICE_HOST"] != null) + { + var kubernetesConfig = new KubernetesProviderConfig(); + var clusterProvider = new KubernetesProvider(kubernetesConfig); + + var remoteConfig = GrpcNetRemoteConfig + .BindToAllInterfaces(advertisedHost: configuration["ProtoActor:AdvertisedHost"]) // Environment variable to be provided by Kubernetes using pod.status.podIP. + .WithLogLevelForDeserializationErrors(LogLevel.Critical) + .WithRemoteDiagnostics(true); + + proto.CreateClusterProvider = _ => clusterProvider; + proto.ConfigureRemoteConfig = _ => remoteConfig; + } + }); + } + + if (useKafka) + { + elsa.UseKafka(kafka => + { + kafka.ConfigureOptions(options => configuration.GetSection("Kafka").Bind(options)); + }); + } + + if (useSecrets) + { + elsa + .UseSecrets() + .UseSecretsManagement(management => + { + management.ConfigureOptions(options => configuration.GetSection("Secrets:Management").Bind(options)); + if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) + management.UseEntityFrameworkCore(ef => + { + ef.UseContextPooling = useDbContextPooling; + ef.UseSqlServer(sqlServerConnectionString); + }); + else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) + management.UseEntityFrameworkCore(ef => + { + ef.UseContextPooling = useDbContextPooling; + ef.UsePostgreSql(postgresConnectionString); + }); + else + management.UseEntityFrameworkCore(ef => + { + ef.UseContextPooling = useDbContextPooling; + ef.UseSqlite(sp => sp.GetSqliteConnectionString()); + }); + }) + .UseSecretsApi() + .UseSecretsScripting(); + } + + //elsa.UseRetention(r => + //{ + // r.SweepInterval = TimeSpan.FromHours(5); + // r.AddDeletePolicy("Delete all finished workflows", sp => + // { + // var filter = new RetentionWorkflowInstanceFilter + // { + // WorkflowStatus = WorkflowStatus.Finished + // }; + // return filter; + // }); + //}); + + if (useMultitenancy) + { + elsa.UseTenants(tenants => + { + tenants.ConfigureMultitenancy(options => + { + options.TenantResolverPipelineBuilder + .Append() + .Append() + .Append() + .Append(); + }); + + if (useTenantsFromConfiguration) + { + tenants.UseConfigurationBasedTenantsProvider(options => configuration.GetSection("Multitenancy").Bind(options)); + } + else + { + tenants.UseStoreBasedTenantsProvider(); + + tenants.UseTenantManagement(management => + { + if (persistenceProvider == PersistenceProvider.MongoDb) + management.UseMongoDb(); + if (persistenceProvider == PersistenceProvider.Dapper) + throw new NotSupportedException("Dapper is not supported for tenant management."); + if (persistenceProvider == PersistenceProvider.EntityFrameworkCore) + { + management.UseEntityFrameworkCore(ef => + { + ef.UseContextPooling = useDbContextPooling; + if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) ef.UseSqlite(sqliteConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.Citus) ef.UsePostgreSql(citusConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb) ef.UsePostgreSql(yugabyteDbConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle) + ef.UseOracle(oracleConnectionString, new() + { + SchemaName = "ELSA" + }); +#if !NET9_0 + if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) + ef.UseMySql(mySqlConnectionString); +#endif + if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString); + }); + } + }); + + tenants.UseTenantManagementEndpoints(); + } + }); + + elsa.UseTenantHttpRouting(tenantHttpRouting => + { + // Override the tenant header name with a custom one. + tenantHttpRouting.WithTenantHeader("X-Tenant-ID"); + }); + } + + elsa.UseWebhooks(webhooks => webhooks.ConfigureSinks += options => builder.Configuration.GetSection("Webhooks").Bind(options)); + elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns")); + elsa.AddSwagger(); + elsa.AddFastEndpointsAssembly(); + //ConfigureForTest?.Invoke(elsa); + }); + + // Obfuscate HTTP request headers. + builder.Services.AddActivityStateFilter(); + + // Optionally configure recurring tasks using alternative schedules. + builder.Services.Configure(options => + { + options.Schedule.ConfigureTask(TimeSpan.FromSeconds(300)); + options.Schedule.ConfigureTask(TimeSpan.FromSeconds(300)); + options.Schedule.ConfigureTask(TimeSpan.FromHours(4)); + options.Schedule.ConfigureTask(TimeSpan.FromSeconds(15)); + }); + + builder.Services.Configure(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); }); + builder.Services.Configure(options => options.Ttl = TimeSpan.FromSeconds(10)); + builder.Services.Configure(options => options.CacheDuration = TimeSpan.FromDays(1)); + builder.Services.Configure(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy)); + builder.Services.AddHealthChecks(); + builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*"))); + + + builder.Services.AddScoped(); + } + + ///// + ///// The main entry point for the application made public for end to end testing. + ///// + //[UsedImplicitly] + //public partial class Program + //{ + // /// + // /// Set by the test runner to configure the module for testing. + // /// + // public static Action? ConfigureForTest { get; set; } + //} + + public static void UseWorkflowFramework(this IApplicationBuilder app) + { + //try + //{ + // app.UseMongoLogger(); + //} + //catch (Exception ex) + //{ + // var d = ex; + //} + + // Configure the pipeline. + var env = app.ApplicationServices.GetRequiredService(); + if (env.IsDevelopment()) + app.UseDeveloperExceptionPage(); + + // CORS. + app.UseCors(); + + // Health checks. + //app.MapHealthChecks("/"); + + // Routing used for SignalR. + app.UseRouting(); + + // Multitenancy. + if (useMultitenancy) + app.UseTenants(); + + // Elsa API endpoints for designer. + var routePrefix = app.ApplicationServices.GetRequiredService>().Value.RoutePrefix; + app.UseWorkflowsApi(routePrefix); + + // Captures unhandled exceptions and returns a JSON response. + app.UseJsonSerializationErrorHandler(); + + app.UseWorkflows(); + } +} + diff --git a/src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs b/src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs new file mode 100644 index 0000000..0b2db48 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Contracts/IWorkflowService.cs @@ -0,0 +1,27 @@ +using Caspian.WorkflowFramework.Models; + +namespace Caspian.WorkflowFramework.Contracts; +public interface IWorkflowService +{ + /// + /// Create and executes a new workflow instance. + /// + /// + /// A new object. + /// Create a new workflow instance and executes it and waits for it to complete or reach a suspend point. + Task> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input); + + /// + /// Publishes the specified event. + /// + /// + /// A new object. + Task> PublishEventAsync(PublishEventModel input); + + /// + /// Inquiry last state of workflow instance. + /// + /// + /// A new object. + Task> InquiryStateAsync(InquiryStateModel input); +} diff --git a/src/apps/Caspian.WorkflowFramework/FodyWeavers.xml b/src/apps/Caspian.WorkflowFramework/FodyWeavers.xml new file mode 100644 index 0000000..00e1d9a --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/apps/Caspian.WorkflowFramework/LICENSE.txt b/src/apps/Caspian.WorkflowFramework/LICENSE.txt new file mode 100644 index 0000000..df73626 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/LICENSE.txt @@ -0,0 +1,3 @@ +Terms and Conditions of Use, Reproduction and Distribution are permitted only for Caspian Company. + +Copyright [2025] Caspian Company \ No newline at end of file diff --git a/src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs b/src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs new file mode 100644 index 0000000..917adc5 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Models/CreateAndExecuteWorkflowModel.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Elsa.Common.Models; +using JetBrains.Annotations; + +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class CreateAndExecuteWorkflowModel +{ + /// + /// Workflow name + /// + [Required] + public string? Name { get; set; } + + /// + /// Workflow version + /// + [Required] + public VersionOptions Version { get; set; } + + /// + /// Current user's id + /// + [Required] + public long? UserId { get; set; } +} diff --git a/src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs b/src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs new file mode 100644 index 0000000..a549da8 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Models/InquiryStateModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Caspian.WorkflowFramework.Models; +public class InquiryStateModel +{ + [Required] + public string WorkflowInstanceId { get; set; } +} diff --git a/src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs b/src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs new file mode 100644 index 0000000..10cd5ad --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Models/PublishEventModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class PublishEventModel +{ + [Required] + public string WorkflowInstanceId { get; set; } + + [Required] + public string EventName { get; set; } + + [Required] + public string? EventData { get; set; } +} diff --git a/src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs b/src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs new file mode 100644 index 0000000..36fadfe --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Models/WorkflowModel.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class WorkflowModel +{ + /// + /// شناسه گردش کار + /// + public string? InstanceId { get; set; } + + /// + /// نام گردش کار + /// + public string? Name { get; set; } + + /// + /// اتمام گردش کار؟ + /// + public bool IsComplete { get; set; } + + /// + /// برای کدام کاربر؟ + /// + public long? UserId { get; set; } + + /// + /// داده صفحه جاری + /// + public string? CurrentPageData { get; set; } + + /// + /// داده صفحه بعد + /// + /// فعلا استفاده ای نداریم + /// + /// + public string? NextPageData { get; set; } + + /// + /// آدرس صفحه بعدی + /// + public string? NextPageUrl { get; set; } + + /// + /// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود + /// + public string? NextPageEventName { get; set; } +} diff --git a/src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs b/src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs new file mode 100644 index 0000000..494b5b3 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Models/WorkflowResponse.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class WorkflowResponse : WorkflowResponseBase where T : class, new() +{ + public WorkflowResponse() + { + Data = new T(); + } + + /// + /// پاسخ + /// + public T? Data { get; set; } +} + +[PublicAPI] +public class WorkflowResponseBase +{ + /// + /// وضعیت پاسخ + /// + public int? Status { get; set; } + + /// + /// پیام پاسخ + /// + public string? Message { get; set; } +} diff --git a/src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs b/src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs new file mode 100644 index 0000000..7ca42ac --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Services/WorkflowService.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Azure; +using Caspian.WorkflowFramework.Contracts; +using Caspian.WorkflowFramework.Models; +using Caspian.WorkflowFramework.Utility; +using Elsa.Common.Models; +using Elsa.JavaScript.Handlers; +using Elsa.Workflows.Management; +using Elsa.Workflows.Management.Filters; +using Elsa.Workflows.Models; +using Elsa.Workflows.Runtime; +using Elsa.Workflows.Runtime.Messages; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Win32; +using San.MongoLogger; +using Newtonsoft.Json; +using System.Diagnostics; + +namespace Caspian.WorkflowFramework.Services; +public class WorkflowService : IWorkflowService, ILoggable +{ + #region Fields + private readonly IServiceProvider _serviceProvider; + private readonly IWorkflowRuntime _workflowRuntime; + private readonly IWorkflowDefinitionService _workflowDefinitionService; + private readonly IWorkflowInstanceVariableManager _workflowInstanceVariableManager; + private readonly IWorkflowInstanceManager _workflowInstanceManager; + private readonly IEventPublisher _eventPublisher; + private readonly IMdbLogger _logger; + #endregion + + #region Ctor + public WorkflowService(IServiceProvider serviceProvider, + IMdbLogger logger) + { + _serviceProvider = serviceProvider; + _workflowRuntime = _serviceProvider.GetRequiredService(); + _workflowDefinitionService = _serviceProvider.GetRequiredService(); + _workflowInstanceVariableManager = _serviceProvider.GetRequiredService(); + _workflowInstanceManager = _serviceProvider.GetRequiredService(); + _eventPublisher = _serviceProvider.GetRequiredService(); + _logger = logger; + } + #endregion + + #region CreateAndExecuteAsync + /// + public async Task> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of CreateAndExecuteAsync", input); + + WorkflowResponse response; + + var workflowClient = await _workflowRuntime.CreateClientAsync(); + + var definition = await _workflowDefinitionService + .FindWorkflowDefinitionAsync(new WorkflowDefinitionFilter() + { + Name = input.Name + }); + + if (definition == null) + { + var msg = $"Workflow '{input.Name}' defintion not found"; + response = new WorkflowResponse() + { + Status = 1, + Message = msg, + Data = null + }; + + sw.Stop(); + + _logger.Log("End of CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, response, sw.ElapsedMilliseconds); + + return response; + } + + var instanceInput = new Dictionary { ["UserId"] = input.UserId }; + input.Version = VersionOptions.Published; + var newInstance = await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest + { + WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definition.DefinitionId, input.Version), + Input = instanceInput, + ExternalUserId = input.UserId + }); + + var result = await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty); + + var variables = await _workflowInstanceVariableManager.GetVariablesAsync(result.WorkflowInstanceId); + + // تبدیل به دیکشنری + var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value); + + var workflowModel = dictionary.ConvertToWorkflowModel(); + workflowModel.InstanceId = workflowClient.WorkflowInstanceId; + workflowModel.UserId = input.UserId; + + response = new WorkflowResponse() + { + Status = 0, + Message = "Success", + Data = workflowModel + }; + + sw.Stop(); + + _logger.Log("End of CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, response, sw.ElapsedMilliseconds); + + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse() + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + + _logger.Log("Exception occurs in CreateAndExecuteAsync", WFInstanceId: response.Data?.InstanceId, response, sw.ElapsedMilliseconds, ex); + + return response; + } + } + #endregion + + #region PublishEventAsync + /// + public async Task> PublishEventAsync(PublishEventModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, input); + + WorkflowResponse response; + + var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId); + + if (instance == null) + { + var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found"; + response = new WorkflowResponse() + { + Status = 1, + Message = msg, + Data = null + }; + + sw.Stop(); + + _logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds); + + return response; + } + + await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, input.EventData); + + var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId); + + // تبدیل به دیکشنری + var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value); + + var workflowModel = dictionary.ConvertToWorkflowModel(); + workflowModel.InstanceId = instance.Id; + workflowModel.UserId = instance.ExternalUserId; + + response = new WorkflowResponse() + { + Status = 0, + Message = "Success", + Data = workflowModel + }; + + sw.Stop(); + + _logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds); + + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse() + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + + _logger.Log("Exception occurs in PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex); + + return response; + } + } + #endregion + + #region InquiryStateAsync + /// + public async Task> InquiryStateAsync(InquiryStateModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, input); + + WorkflowResponse response; + + var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId); + + if (instance == null) + { + var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found"; + response = new WorkflowResponse() + { + Status = 1, + Message = msg, + Data = null + }; + + sw.Stop(); + + _logger.Log("End of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds); + + return response; + } + + var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId); + + // تبدیل به دیکشنری + var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value); + + var workflowModel = dictionary.ConvertToWorkflowModel(); + workflowModel.InstanceId = instance.Id; + workflowModel.UserId = instance.ExternalUserId; + + response = new WorkflowResponse() + { + Status = 0, + Message = "Success", + Data = workflowModel + }; + + sw.Stop(); + + _logger.Log("End of InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds); + + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse() + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + + _logger.Log("Exception occurs in InquiryStateAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex); + + return response; + } + } + #endregion +} diff --git a/src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs b/src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs new file mode 100644 index 0000000..b917ae1 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/Utility/ConversionExtensions.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Caspian.WorkflowFramework.Models; + +namespace Caspian.WorkflowFramework.Utility; +public static class ConversionExtensions +{ + public static WorkflowModel ConvertToWorkflowModel(this Dictionary dictionary) + { + var workflowModel = new WorkflowModel(); + + if (dictionary == null || dictionary.Count == 0) + return workflowModel; + + //Map dictionary values to WorkflowModel properties + workflowModel.InstanceId = dictionary.ContainsKey(nameof(workflowModel.InstanceId)) + ? dictionary[nameof(workflowModel.InstanceId)]?.ToString() + : null; + + workflowModel.Name = dictionary.ContainsKey(nameof(workflowModel.Name)) + ? dictionary[nameof(workflowModel.Name)]?.ToString() + : null; + + workflowModel.IsComplete = dictionary.ContainsKey(nameof(workflowModel.IsComplete)) + ? bool.TryParse(dictionary[nameof(workflowModel.IsComplete)]?.ToString(), out var isComplete) && isComplete + : false; + + workflowModel.UserId = dictionary.ContainsKey(nameof(workflowModel.UserId)) + ? long.TryParse(dictionary[nameof(workflowModel.UserId)]?.ToString(), out var userId) ? userId : (long?)null + : null; + + //workflowModel.CurrentPageData = dictionary.ContainsKey(nameof(workflowModel.CurrentPageData)) + // ? (object?)dictionary[nameof(workflowModel.CurrentPageData)] + // : null; + + workflowModel.CurrentPageData = dictionary.ContainsKey(nameof(workflowModel.CurrentPageData)) + ? dictionary[nameof(workflowModel.CurrentPageData)]?.ToString() + : null; + + //workflowModel.NextPageData = dictionary.ContainsKey(nameof(workflowModel.NextPageData)) + // ? (object?)dictionary[nameof(workflowModel.NextPageData)] + // : null; + + workflowModel.NextPageData = dictionary.ContainsKey(nameof(workflowModel.NextPageData)) + ? dictionary[nameof(workflowModel.NextPageData)]?.ToString() + : null; + + workflowModel.NextPageUrl = dictionary.ContainsKey(nameof(workflowModel.NextPageUrl)) + ? dictionary[nameof(workflowModel.NextPageUrl)]?.ToString() + : null; + + workflowModel.NextPageEventName = dictionary.ContainsKey(nameof(workflowModel.NextPageEventName)) + ? dictionary[nameof(workflowModel.NextPageEventName)]?.ToString() + : null; + + return workflowModel; + } +} diff --git a/src/apps/Caspian.WorkflowFramework/favicon.ico b/src/apps/Caspian.WorkflowFramework/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f39a7b93d37dd9e9a9d508ec22c39fd1ad644019 GIT binary patch literal 1150 zcmah|dq`7Z6hHQoFdv1K1erY~6N(^21u4`XRuX{{q7_AdXi;ez1fd90^HG|qX}XCs z-Ey{BYHB$(AAw1wNt)Z;-MzbeXo~sFl#*`eGrm~;QQ!D>w%_@kbM866a{(OiX%3q&@D4&*2^iRI*rmC>?XZ z%L=yl%?@{NVe(KPDdv#Eq#k+WeK{eNBh-iHG8m`B9?ebBu@t9v#?-KlEyP&Vm#^@6 z!D9W9mWX8HXHa{kK1#}?}g1cb4P$K+^|0Es4Ng~0KmQ=fFM*fU=bh? XZ}9>^25xXKzI9#Q_};hzEMn|Wr8BQk literal 0 HcmV?d00001 diff --git a/src/apps/Caspian.WorkflowFramework/gallery-icon.png b/src/apps/Caspian.WorkflowFramework/gallery-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c401de4ecb789d526ef9342692bc919f74287cd7 GIT binary patch literal 4535 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 E0OKfCh5!Hn literal 0 HcmV?d00001 diff --git a/src/apps/Caspian.WorkflowFramework/nuget_command.txt b/src/apps/Caspian.WorkflowFramework/nuget_command.txt new file mode 100644 index 0000000..7e09d5e --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/nuget_command.txt @@ -0,0 +1 @@ +nuget pack Caspian.WorkflowFramework.nuspec -IncludeReferencedProjects \ No newline at end of file diff --git a/src/apps/Caspian.WorkflowFramework/package-icon.png b/src/apps/Caspian.WorkflowFramework/package-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..05adb12c955740886ab4ed72187d3df41f3d4cee GIT binary patch literal 12783 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 Z?4ADqOh&A-)PD?^tfZ1eotSaR{{fwDWHkT) literal 0 HcmV?d00001 diff --git a/src/apps/Caspian.WorkflowFramework/readme.md b/src/apps/Caspian.WorkflowFramework/readme.md new file mode 100644 index 0000000..c1954a1 --- /dev/null +++ b/src/apps/Caspian.WorkflowFramework/readme.md @@ -0,0 +1,35 @@ +# Caspian.NextBank.Workflow + +Workflows for project "NextBankParsian" + +## Installation +```bash +dotnet add package Caspian.NextBank.Workflow +``` +```bash +install-package Caspian.NextBank.Workflow +``` + +## Usage Example +In your Program.cs : +```csharp +builder.Services.AddMongoLogger(builder.Configuration); + +builder.AddWorkflowServices("DefaultConnection"); + +var app = builder.Build(); + +app.UseMongoLogger(); + +app.UseWorkflow(); +``` +Then use service "WfService" + +## Features +- Start new instance of every workflow +- Publish event and get next step information +- Get last status of workflow instance + +## Version History +### 1.0.0-alfa +- Initial prerelease \ No newline at end of file diff --git a/src/apps/Elsa.Server.Web/appsettings.json b/src/apps/Elsa.Server.Web/appsettings.json index 737250e..818f03e 100644 --- a/src/apps/Elsa.Server.Web/appsettings.json +++ b/src/apps/Elsa.Server.Web/appsettings.json @@ -5,6 +5,33 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "LogDatabaseSettings": { + "ConnectionString": "mongodb://localhost", + "DatabaseName": "Elsa.Server.Web.DB", + "LogsCollectionName": "Col", + "BatchSize": 0, + "RollingInterval": "Daily", + "MaskList": [ + "code", + "depositNumber", + "username", + "customerNo", + "password", + "sessionId", + "mainAccount", + "accountNumber", + "owners", + "balance", + "accountNo", + "depositUniqueId", + "externalRefList", + "externalref", + "sameSignatureDepositNumber", + "mobileNumber", + "destinationNumber", + "cardNumber" + ] + }, "HostBuilder": { "reloadConfigOnChange": true }, @@ -24,7 +51,7 @@ "RabbitMq": "", //"amqp://guest:guest@localhost:5672", "Redis": "" //"localhost:6379,abortConnect=false" }, - "DatabaseProvider": "SqlServer",//"Sqlite", + "DatabaseProvider": "SqlServer", //"Sqlite", "Multitenancy": { "Tenants": [ { diff --git a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json index 6878f72..c2f42a4 100644 --- a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json +++ b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json @@ -7,6 +7,33 @@ "OpenTelemetry": "Debug" } }, + "LogDatabaseSettings": { + "ConnectionString": "mongodb://localhost", + "DatabaseName": "Elsa.ServerAndStudio.Web.DB", + "LogsCollectionName": "Col", + "BatchSize": 0, + "RollingInterval": "Daily", + "MaskList": [ + "code", + "depositNumber", + "username", + "customerNo", + "password", + "sessionId", + "mainAccount", + "accountNumber", + "owners", + "balance", + "accountNo", + "depositUniqueId", + "externalRefList", + "externalref", + "sameSignatureDepositNumber", + "mobileNumber", + "destinationNumber", + "cardNumber" + ] + }, "AllowedHosts": "*", "ConnectionStrings": { "Sqlite": "", //"Data Source=elsa.sqlite.db;Cache=Shared;",