remove workflows 's dedicated appsettings.json and change registration of it
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using Caspian.WorkflowFramework;
|
using Caspian.WorkflowFramework;
|
||||||
using Caspian.WorkflowFramework.Sample;
|
using Caspian.WorkflowFramework.Sample;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using San.MongoLogger;
|
using San.MongoLogger;
|
||||||
@@ -29,9 +29,8 @@ builder.Services.AddSwaggerGen(c =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Workflow
|
//Workflow
|
||||||
//var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||||
var t = $"appsettings.workflow.{builder.Environment.EnvironmentName}.json";
|
builder.AddWorkflowFramework(connectionString);
|
||||||
builder.AddWorkflowFramework($"appsettings.workflow.json");
|
|
||||||
|
|
||||||
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
|
builder.Services.AddMongoLogger<LogModel>(builder.Configuration);
|
||||||
|
|
||||||
@@ -61,6 +60,7 @@ app.MapControllers();
|
|||||||
//Workflow
|
//Workflow
|
||||||
app.UseWorkflowFramework();
|
app.UseWorkflowFramework();
|
||||||
|
|
||||||
|
|
||||||
// Redirect to Swagger URL
|
// Redirect to Swagger URL
|
||||||
app.Use(async (context, next) =>
|
app.Use(async (context, next) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,285 +38,6 @@
|
|||||||
"reloadConfigOnChange": true
|
"reloadConfigOnChange": true
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Data Source=.;Initial Catalog=WorkflowFrameworkTest;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;",
|
"DefaultConnection": "Data Source=.;Initial Catalog=WfDb;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"
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
//"Microsoft.EntityFrameworkCore.Database.Command": "Warning" // Disable SQL command logging
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +1,60 @@
|
|||||||
using System.IO;
|
using Caspian.WorkflowFramework.Contracts;
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using Caspian.WorkflowFramework.Contracts;
|
|
||||||
using Caspian.WorkflowFramework.Services;
|
using Caspian.WorkflowFramework.Services;
|
||||||
using Elsa.Alterations.Extensions;
|
|
||||||
using Elsa.Alterations.MassTransit.Extensions;
|
|
||||||
using Elsa.Caching.Options;
|
using Elsa.Caching.Options;
|
||||||
using Elsa.Common.Codecs;
|
|
||||||
using Elsa.Common.DistributedHosting.DistributedLocks;
|
|
||||||
using Elsa.Common.RecurringTasks;
|
using Elsa.Common.RecurringTasks;
|
||||||
using Elsa.Common.Serialization;
|
using Elsa.Common.Serialization;
|
||||||
using Elsa.Dapper.Extensions;
|
|
||||||
using Elsa.Dapper.Services;
|
|
||||||
using Elsa.DropIns.Extensions;
|
|
||||||
using Elsa.EntityFrameworkCore.Extensions;
|
using Elsa.EntityFrameworkCore.Extensions;
|
||||||
using Elsa.EntityFrameworkCore.Modules.Alterations;
|
using Elsa.EntityFrameworkCore.Modules.Alterations;
|
||||||
using Elsa.EntityFrameworkCore.Modules.Identity;
|
using Elsa.EntityFrameworkCore.Modules.Identity;
|
||||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||||
using Elsa.EntityFrameworkCore.Modules.Tenants;
|
using Elsa.EntityFrameworkCore.Modules.Tenants;
|
||||||
using Elsa.Expressions.Helpers;
|
|
||||||
using Elsa.Extensions;
|
using Elsa.Extensions;
|
||||||
using Elsa.Features.Services;
|
|
||||||
using Elsa.Identity.Multitenancy;
|
|
||||||
using Elsa.Kafka;
|
|
||||||
using Elsa.Kafka.Factories;
|
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.OpenTelemetry.Middleware;
|
||||||
using Elsa.Retention.Extensions;
|
|
||||||
using Elsa.Retention.Models;
|
|
||||||
using Elsa.Secrets.Extensions;
|
|
||||||
using Elsa.Secrets.Management.Tasks;
|
using Elsa.Secrets.Management.Tasks;
|
||||||
using Elsa.Secrets.Persistence;
|
using Elsa.Secrets.Persistence;
|
||||||
using Elsa.Server.Web;
|
using Elsa.Server.Web;
|
||||||
using Elsa.Server.Web.Extensions;
|
|
||||||
using Elsa.Server.Web.Filters;
|
|
||||||
using Elsa.Server.Web.Messages;
|
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;
|
||||||
using Elsa.Workflows.Api;
|
|
||||||
using Elsa.Workflows.CommitStates.Strategies;
|
|
||||||
using Elsa.Workflows.IncidentStrategies;
|
using Elsa.Workflows.IncidentStrategies;
|
||||||
using Elsa.Workflows.LogPersistence;
|
using Elsa.Workflows.LogPersistence;
|
||||||
using Elsa.Workflows.Management;
|
using Elsa.Workflows.Management;
|
||||||
using Elsa.Workflows.Management.Stores;
|
|
||||||
using Elsa.Workflows.Options;
|
using Elsa.Workflows.Options;
|
||||||
using Elsa.Workflows.Runtime.Distributed.Extensions;
|
using Elsa.Workflows.Runtime.Distributed.Extensions;
|
||||||
using Elsa.Workflows.Runtime.Options;
|
using Elsa.Workflows.Runtime.Options;
|
||||||
using Elsa.Workflows.Runtime.Stores;
|
|
||||||
using Elsa.Workflows.Runtime.Tasks;
|
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.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
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;
|
||||||
using San.MongoLogger.Models;
|
using San.MongoLogger.Models;
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
namespace Caspian.WorkflowFramework;
|
namespace Caspian.WorkflowFramework;
|
||||||
|
|
||||||
public static class DependencyInjection
|
public static class DependencyInjection
|
||||||
{
|
{
|
||||||
const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore;
|
public static void AddWorkflowFramework(this IHostApplicationBuilder builder, string sqlServerConnectionString)
|
||||||
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()
|
//var configuration = new ConfigurationBuilder()
|
||||||
.AddJsonFile(appsettingName, optional: false, reloadOnChange: true)
|
// .AddJsonFile(appsettingName, optional: false, reloadOnChange: true)
|
||||||
.Build();
|
// .Build();
|
||||||
//var configuration = builder.Configuration;
|
//var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!;
|
||||||
var heartbeatSection = configuration.GetSection("Heartbeat");
|
|
||||||
var identitySection = configuration.GetSection("Identity");
|
var sqlDatabaseProvider = Enum.Parse<SqlDatabaseProvider>("SqlServer");
|
||||||
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<ApplicationRole>(configuration["AppRole"]! ?? "Default");
|
|
||||||
var sqlDatabaseProvider = Enum.Parse<SqlDatabaseProvider>(configuration["DatabaseProvider"] ?? "SqlServer");
|
|
||||||
const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore;
|
|
||||||
const bool useDbContextPooling = false;
|
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 runEFCoreMigrations = true;
|
||||||
const bool useMemoryStores = false;
|
|
||||||
const bool useCaching = true;
|
const bool useCaching = true;
|
||||||
const bool useKafka = false;
|
|
||||||
const bool useReadOnlyMode = false;
|
const bool useReadOnlyMode = false;
|
||||||
const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
|
|
||||||
const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed;
|
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 disableVariableWrappers = false;
|
||||||
const bool disableVariableCopying = false;
|
const bool disableVariableCopying = false;
|
||||||
const bool useManualOtelInstrumentation = false;
|
|
||||||
|
|
||||||
// Optionally create type aliases for easier configuration.
|
// Optionally create type aliases for easier configuration.
|
||||||
TypeAliasRegistry.RegisterAlias("OrderReceivedProducerFactory", typeof(GenericProducerFactory<string, OrderReceived>));
|
TypeAliasRegistry.RegisterAlias("OrderReceivedProducerFactory", typeof(GenericProducerFactory<string, OrderReceived>));
|
||||||
TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory<string, OrderReceived>));
|
TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory<string, OrderReceived>));
|
||||||
|
|
||||||
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.
|
// Add Elsa services.
|
||||||
builder.Services.AddScoped<IPropertyUIHandler, CustomCheckListOptionsProvider>();
|
builder.Services.AddScoped<IPropertyUIHandler, CustomCheckListOptionsProvider>();
|
||||||
|
|
||||||
@@ -216,7 +70,8 @@ public static class DependencyInjection
|
|||||||
workflows.UseCommitStrategies(strategies =>
|
workflows.UseCommitStrategies(strategies =>
|
||||||
{
|
{
|
||||||
strategies.AddStandardStrategies();
|
strategies.AddStandardStrategies();
|
||||||
strategies.Add("Every 10 seconds", new PeriodicWorkflowStrategy(TimeSpan.FromSeconds(10)));
|
//می خوایم سریع انجام شه
|
||||||
|
//strategies.Add("Every 10 seconds", new PeriodicWorkflowStrategy(TimeSpan.FromSeconds(10)));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.UseWorkflowManagement(management =>
|
.UseWorkflowManagement(management =>
|
||||||
@@ -226,15 +81,6 @@ public static class DependencyInjection
|
|||||||
ef.UseSqlServer(sqlServerConnectionString);
|
ef.UseSqlServer(sqlServerConnectionString);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (useZipCompression)
|
|
||||||
management.SetCompressionAlgorithm(nameof(Zstd));
|
|
||||||
|
|
||||||
if (useMemoryStores)
|
|
||||||
management.UseWorkflowInstances(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService<MemoryWorkflowInstanceStore>());
|
|
||||||
|
|
||||||
if (useMassTransit)
|
|
||||||
management.UseMassTransitDispatcher();
|
|
||||||
|
|
||||||
if (useCaching)
|
if (useCaching)
|
||||||
management.UseCache();
|
management.UseCache();
|
||||||
|
|
||||||
@@ -248,7 +94,6 @@ public static class DependencyInjection
|
|||||||
{
|
{
|
||||||
ef.UseContextPooling = useDbContextPooling;
|
ef.UseContextPooling = useDbContextPooling;
|
||||||
|
|
||||||
//ef.UseSqlServer(sqlServerConnectionString, new ElsaDbContextOptions);
|
|
||||||
var migrationsAssembly = typeof(Elsa.EntityFrameworkCore.SqlServer.IdentityDbContextFactory).Assembly;
|
var migrationsAssembly = typeof(Elsa.EntityFrameworkCore.SqlServer.IdentityDbContextFactory).Assembly;
|
||||||
var connectionString = sqlServerConnectionString;
|
var connectionString = sqlServerConnectionString;
|
||||||
ef.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(migrationsAssembly, connectionString, null, configure => configure.CommandTimeout(60000));
|
ef.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(migrationsAssembly, connectionString, null, configure => configure.CommandTimeout(60000));
|
||||||
@@ -261,69 +106,14 @@ public static class DependencyInjection
|
|||||||
runtime.UseDistributedRuntime();
|
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<MemoryActivityExecutionStore>();
|
|
||||||
runtime.WorkflowExecutionLogStore = sp => sp.GetRequiredService<MemoryWorkflowExecutionLogStore>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useCaching)
|
if (useCaching)
|
||||||
runtime.UseCache();
|
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<Program>();
|
|
||||||
})
|
})
|
||||||
.UseCSharp(options =>
|
.UseCSharp(options =>
|
||||||
{
|
{
|
||||||
options.DisableWrappers = disableVariableWrappers;
|
options.DisableWrappers = disableVariableWrappers;
|
||||||
options.AppendScript("string Greet(string name) => $\"Hello {name}!\";");
|
//options.AppendScript("string Greet(string name) => $\"Hello {name}!\";");
|
||||||
options.AppendScript("string SayHelloWorld() => Greet(\"World\");");
|
//options.AppendScript("string SayHelloWorld() => Greet(\"World\");");
|
||||||
})
|
})
|
||||||
.UseJavaScript(options =>
|
.UseJavaScript(options =>
|
||||||
{
|
{
|
||||||
@@ -331,286 +121,17 @@ public static class DependencyInjection
|
|||||||
options.DisableWrappers = disableVariableWrappers;
|
options.DisableWrappers = disableVariableWrappers;
|
||||||
options.DisableVariableCopying = disableVariableCopying;
|
options.DisableVariableCopying = disableVariableCopying;
|
||||||
options.RegisterType<OrderReceived>();
|
options.RegisterType<OrderReceived>();
|
||||||
options.ConfigureEngine(engine =>
|
//options.ConfigureEngine(engine =>
|
||||||
{
|
//{
|
||||||
engine.Execute("function greet(name) { return `Hello ${name}!`; }");
|
// engine.Execute("function greet(name) { return `Hello ${name}!`; }");
|
||||||
engine.Execute("function sayHelloWorld() { return greet('World'); }");
|
// 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<MySqlClient>("MySql");
|
|
||||||
client.Register<PostgreSqlClient>("PostgreSql");
|
|
||||||
client.Register<SqliteClient>("Sqlite");
|
|
||||||
client.Register<SqlServerClient>("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();
|
.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<OrderReceived>();
|
|
||||||
|
|
||||||
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<HostTenantResolver>()
|
|
||||||
.Append<RoutePrefixTenantResolver>()
|
|
||||||
.Append<HeaderTenantResolver>()
|
|
||||||
.Append<ClaimsTenantResolver>();
|
|
||||||
});
|
|
||||||
|
|
||||||
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<Program>();
|
elsa.AddFastEndpointsAssembly<Program>();
|
||||||
//ConfigureForTest?.Invoke(elsa);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Obfuscate HTTP request headers.
|
|
||||||
builder.Services.AddActivityStateFilter<HttpRequestAuthenticationHeaderFilter>();
|
|
||||||
|
|
||||||
// Optionally configure recurring tasks using alternative schedules.
|
// Optionally configure recurring tasks using alternative schedules.
|
||||||
builder.Services.Configure<RecurringTaskOptions>(options =>
|
builder.Services.Configure<RecurringTaskOptions>(options =>
|
||||||
{
|
{
|
||||||
@@ -624,25 +145,11 @@ public static class DependencyInjection
|
|||||||
builder.Services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(10));
|
builder.Services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(10));
|
||||||
builder.Services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
|
builder.Services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
|
||||||
builder.Services.Configure<IncidentOptions>(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
|
builder.Services.Configure<IncidentOptions>(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
|
||||||
builder.Services.AddHealthChecks();
|
//builder.Services.AddHealthChecks();
|
||||||
builder.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*")));
|
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddScoped<IWorkflowService, WorkflowService>();
|
builder.Services.AddScoped<IWorkflowService, WorkflowService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
///// <summary>
|
|
||||||
///// The main entry point for the application made public for end to end testing.
|
|
||||||
///// </summary>
|
|
||||||
//[UsedImplicitly]
|
|
||||||
//public partial class Program
|
|
||||||
//{
|
|
||||||
// /// <summary>
|
|
||||||
// /// Set by the test runner to configure the module for testing.
|
|
||||||
// /// </summary>
|
|
||||||
// public static Action<IModule>? ConfigureForTest { get; set; }
|
|
||||||
//}
|
|
||||||
|
|
||||||
public static void UseWorkflowFramework(this IApplicationBuilder app)
|
public static void UseWorkflowFramework(this IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
//try
|
//try
|
||||||
@@ -659,27 +166,13 @@ public static class DependencyInjection
|
|||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
|
|
||||||
// CORS.
|
|
||||||
app.UseCors();
|
|
||||||
|
|
||||||
// Health checks.
|
// Health checks.
|
||||||
//app.MapHealthChecks("/");
|
//app.MapHealthChecks("/");
|
||||||
|
|
||||||
// Routing used for SignalR.
|
|
||||||
app.UseRouting();
|
|
||||||
|
|
||||||
// Multitenancy.
|
|
||||||
if (useMultitenancy)
|
|
||||||
app.UseTenants();
|
|
||||||
|
|
||||||
// Elsa API endpoints for designer.
|
|
||||||
var routePrefix = app.ApplicationServices.GetRequiredService<IOptions<ApiEndpointOptions>>().Value.RoutePrefix;
|
|
||||||
app.UseWorkflowsApi(routePrefix);
|
|
||||||
|
|
||||||
// Captures unhandled exceptions and returns a JSON response.
|
// Captures unhandled exceptions and returns a JSON response.
|
||||||
app.UseJsonSerializationErrorHandler();
|
app.UseJsonSerializationErrorHandler();
|
||||||
|
|
||||||
app.UseWorkflows();
|
//app.UseWorkflows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user