From 692937d7d7802264a49bea2a89e70f6555dbb499 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 3 Dec 2024 15:07:55 +0100 Subject: [PATCH] Enhance Multitenancy with Runtime Tenant Management and Task Handling (#6173) * Work in progress: Add DefaultTenantService for tenant management Introduce `DefaultTenantService` and its corresponding interface `ITenantService` to manage tenant operations such as finding, getting, and listing tenants. Update `MultitenantBackgroundService` to utilize `DefaultTenantService` for handling tenant lifecycle events. This enhancement standardizes tenant operations and improves the maintainability of the multitenancy feature. * WIP * Add multitenancy event handlers and task interfaces Implemented new interfaces IBackgroundTaskStarter and ITaskExecutor to manage task lifecycle events efficiently. Introduced new classes such as RunBackgroundTasks, RunStartupTasks, and StartRecurringTasks for handling tenant activation and deactivation events. Modified TaskExecutor to implement these interfaces and adjusted tenant registration logic to invoke these new handlers. * Refactor multitenancy and task management services. Remove background and recurring task runners, and integrate tenant activation and deactivation into the multitenancy feature. Enable multitenancy in the server application and create a new service for tenant activation and deactivation. This refactor simplifies the management of tenant-specific tasks and enhances the modularity of the platform. * Refactor background service to use startup tasks Replaced hosted service implementation with startup tasks for executing multi-tenant tasks and EF Core migrations. Introduced `PriorityAttribute` to manage task execution order, ensuring migrations run before other services that require database access. This simplifies tenant activation with an ordered task execution and removes redundant classes. * Refactor MultitenancyFeature service registrations Reorganized service registrations for better clarity and maintainability. Changed the registration of some services to use factory delegates for retrieving existing services to ensure correct dependencies. This refactor improves the flexibility of the tenant lifecycle event handling. * Update V3_3 migration files * Add tenant management endpoints and enhance tenant handling Implemented tenant management endpoints including Add, Get, List, and Update. Enhanced tenant handling by introducing configuration and store-based providers, and improved error logging for tenant updates. Adjusted various internal functionalities to better support multitenancy features through different persistence providers. * Implement tenant deletion endpoint and refactor migration setup. Introduce a new API endpoint to handle tenant deletions while providing appropriate responses based on successful or unsuccessful attempts. Refactor migration handling by replacing startup tasks with hosted services across various modules to streamline the migration execution process. * Add and integrate ConfigurationJsonConverter Introduce a `ConfigurationJsonConverter` to handle JSON serialization and deserialization of `IConfiguration` objects. This change centralizes configuration serialization logic, leading to cleaner and more maintainable code. Updated various parts of the codebase to use the new serialization utility, ensuring a consistent approach throughout the application. * Refactor JSON conversion and update tenant endpoint. Removed unused workflow references and streamlined JSON handling in `ConfigurationJsonConverter`. Simplified tenant ID handling by removing `IIdentityGenerator` and setting a default value for `UpdatedTenant.Id`. * Add logging for cancelled recurring tasks Integrated ILogger to log information when a recurring task is canceled due to an OperationCanceledException. This change enhances troubleshooting by providing clearer insights into task cancellations and their underlying reasons, improving maintainability and observability of the task execution process. * Disable multitenancy support and adjust default Tenant ID. Multitenancy is now disabled by setting 'useMultitenancy' to false in the configuration. Additionally, the default Tenant's ID has been changed from null to an empty string to prevent potential null reference issues. * Remove MultitenantHostedService abstraction file The MultitenantHostedService.cs file was removed as it is no longer necessary. Its responsibilities have likely been refactored or integrated into another service, indicating a simplification or restructuring of the multitenancy handling in the codebase. * Rename PriorityAttribute to OrderAttribute for clarity. This change improves the clarity of the code by renaming PriorityAttribute to OrderAttribute, reflecting its actual purpose. All occurrences of the attribute in the codebase have been updated accordingly to maintain consistency. This makes the intent of the code more understandable for future maintenance and development. * Fix message key retrieval in ProduceMessage activity Update the ProduceMessage activity to use GetOrDefault for retrieving the message key. This change ensures that a null key is used if no explicit key is provided or if the key is empty or whitespace, preventing potential errors during message production. * Refactor multitenancy and scheduling services. Removed DefaultTenantContextInitializer interface and class, refactored tenant activation/deactivation to use try-catch logging, and updated tenant context handling to use IDisposable for context push. New activities and workflows added in Elsa.Server.Web, and scheduling services enhanced to schedule jobs with explicit job keys and groups. Also, adjusted configurations to enable multitenancy, providing improved maintainability and flexibility. * Remove Example1 activities and disable multitenancy Deleted Example1Activity, Example1Workflow, and FirstActivity classes to clean up unused code and simplify the codebase. Disabled multitenancy by setting useMultitenancy to false, likely to streamline configuration and resource utilization. * Fix and normalize URL path concatenation. Ensure that the base URLs in both base path providers consistently end with a forward slash. This normalization prevents potential issues with endpoint routing and path concatenation, improving overall URL construction robustness. --- .github/workflows/packages.yml | 2 +- Directory.Packages.props | 2 + scripts/migrations/efcore-3.3.sh | 2 +- .../Elsa.Server.Web/Elsa.Server.Web.csproj | 4 - src/apps/Elsa.Server.Web/Program.cs | 61 ++++++--- src/apps/Elsa.Server.Web/appsettings.json | 18 ++- .../Services/TestTenantsProvider.cs | 9 +- src/modules/Elsa.Common/Elsa.Common.csproj | 1 + .../Elsa.Common.csproj.DotSettings | 2 + .../Features/BackgroundTasksFeature.cs | 18 --- .../Features/MultitenancyFeature.cs | 24 +++- .../MultitenantBackgroundService.cs | 56 -------- .../Abstractions/MultitenantHostedService.cs | 31 ----- .../Abstractions/TenantResolverBase.cs | 5 - .../Contracts/IBackgroundTaskStarter.cs | 7 + .../Multitenancy/Contracts/ITaskExecutor.cs | 6 + .../Multitenancy/Contracts/ITenantAccessor.cs | 4 +- .../Contracts/ITenantActivatedEvent.cs | 6 + .../Contracts/ITenantContextInitializer.cs | 6 - .../Contracts/ITenantDeactivatedEvent.cs | 6 + .../Multitenancy/Contracts/ITenantService.cs | 68 ++++++++++ .../Contracts/ITenantsProvider.cs | 4 +- .../Multitenancy/Entities/Tenant.cs | 2 +- .../EventArgs/TenantActivatedEventArgs.cs | 3 + .../EventArgs/TenantDeactivatedEventArgs.cs | 3 + .../Multitenancy/EventArgs/TenantEventArgs.cs | 3 + .../EventHandlers/RunBackgroundTasks.cs | 34 +++++ .../EventHandlers/RunStartupTasks.cs | 19 +++ .../EventHandlers/StartRecurringTasks.cs | 49 +++++++ .../HostedServices/ActivateTenants.cs | 18 +++ .../HostedServices/BackgroundTasksRunner.cs | 27 ---- .../HostedServices/RecurringTasksRunner.cs | 36 ----- .../HostedServices/StartupTasksRunner.cs | 18 --- .../HostedServices/TaskExecutor.cs | 27 ---- .../Implementations/DefaultTenantAccessor.cs | 25 +++- .../DefaultTenantContextInitializer.cs | 10 -- .../Implementations/DefaultTenantResolver.cs | 2 +- .../Implementations/DefaultTenantService.cs | 124 ++++++++++++++++++ .../Implementations/DefaultTenantsProvider.cs | 8 +- .../Implementations/TaskExecutor.cs | 42 ++++++ .../Implementations/TenantEventsManager.cs | 36 +++++ .../Multitenancy/Models/TenantFilter.cs | 2 + .../Multitenancy/Models/TenantScope.cs | 13 +- .../RecurringTasks/OrderAttribute.cs | 10 ++ .../ConfigurationJsonConverter.cs | 45 +++++++ .../Serialization/SerializerOptions.cs | 11 ++ .../Elsa.Common/Serialization/Serializers.cs | 25 ++++ .../Elsa.Common/Services/MemoryStore.cs | 5 + .../PersistenceFeatureBase.cs | 9 +- .../RunMigrationsHostedService.cs | 32 +++-- .../RunMigrationsStartupTask.cs | 22 ++++ .../DbContextFactories.cs | 24 ++-- .../Alterations/20231015122151_Initial.cs | 3 +- .../Alterations/20240329200618_V3_1.cs | 4 +- .../Alterations/20240610184517_V3_2.cs | 4 +- ...ner.cs => 20241202095004_V3_3.Designer.cs} | 2 +- ...9102522_V3_3.cs => 20241202095004_V3_3.cs} | 0 .../Identity/20231015122238_Initial.cs | 3 +- .../Identity/20240329200746_V3_1.cs | 4 +- .../Identity/20240610184903_V3_2.cs | 4 +- ...ner.cs => 20241202095402_V3_3.Designer.cs} | 2 +- ...9102921_V3_3.cs => 20241202095402_V3_3.cs} | 0 .../Labels/20231015122253_Initial.cs | 3 +- .../Migrations/Labels/20240329200817_V3_1.cs | 4 +- .../Migrations/Labels/20240610184957_V3_2.cs | 4 +- ...ner.cs => 20241202095515_V3_3.Designer.cs} | 2 +- ...9103004_V3_3.cs => 20241202095515_V3_3.cs} | 0 .../Management/20231015122223_Initial.cs | 3 +- .../Management/20240329200720_V3_1.cs | 4 +- .../Management/20240610184802_V3_2.cs | 4 +- ...ner.cs => 20241202095327_V3_3.Designer.cs} | 2 +- ...9102839_V3_3.cs => 20241202095327_V3_3.cs} | 0 .../Runtime/20231024160940_Initial.cs | 3 +- .../Migrations/Runtime/20240329200651_V3_1.cs | 4 +- .../Migrations/Runtime/20240610184652_V3_2.cs | 4 +- ...ner.cs => 20241202095210_V3_3.Designer.cs} | 2 +- ...9102737_V3_3.cs => 20241202095210_V3_3.cs} | 0 .../Tenants/20241202095623_V3_3.Designer.cs | 54 ++++++++ .../Migrations/Tenants/20241202095623_V3_3.cs | 68 ++++++++++ .../TenantsElsaDbContextModelSnapshot.cs | 51 +++++++ .../DbContextFactories.cs | 24 ++-- ...ner.cs => 20241202095130_V3_3.Designer.cs} | 2 +- ...9102700_V3_3.cs => 20241202095130_V3_3.cs} | 0 ...ner.cs => 20241202095500_V3_3.Designer.cs} | 2 +- ...9102957_V3_3.cs => 20241202095500_V3_3.cs} | 0 ...ner.cs => 20241202095616_V3_3.Designer.cs} | 2 +- ...9103034_V3_3.cs => 20241202095616_V3_3.cs} | 0 ...ner.cs => 20241202095357_V3_3.Designer.cs} | 2 +- ...9102913_V3_3.cs => 20241202095357_V3_3.cs} | 0 ...ner.cs => 20241202095317_V3_3.Designer.cs} | 2 +- ...9102830_V3_3.cs => 20241202095317_V3_3.cs} | 0 .../Tenants/20241202095654_V3_3.Designer.cs | 57 ++++++++ .../Migrations/Tenants/20241202095654_V3_3.cs | 60 +++++++++ .../TenantsElsaDbContextModelSnapshot.cs | 54 ++++++++ .../DbContextFactories.cs | 24 ++-- .../Alterations/20231015122203_Initial.cs | 3 +- .../Alterations/20240329200643_V3_1.cs | 4 +- .../Alterations/20240610184626_V3_2.cs | 4 +- ...ner.cs => 20241202095104_V3_3.Designer.cs} | 2 +- ...9102646_V3_3.cs => 20241202095104_V3_3.cs} | 0 .../Identity/20231015122250_Initial.cs | 3 +- .../Identity/20240329200810_V3_1.cs | 4 +- .../Identity/20240610184941_V3_2.cs | 4 +- ...ner.cs => 20241202095450_V3_3.Designer.cs} | 2 +- ...9102950_V3_3.cs => 20241202095450_V3_3.cs} | 0 .../Labels/20231015122304_Initial.cs | 3 +- .../Migrations/Labels/20240329200841_V3_1.cs | 4 +- .../Migrations/Labels/20240610185037_V3_2.cs | 4 +- ...ner.cs => 20241202095605_V3_3.Designer.cs} | 2 +- ...9103027_V3_3.cs => 20241202095605_V3_3.cs} | 0 .../Management/20231015122234_Initial.cs | 3 +- .../Management/20240329200740_V3_1.cs | 4 +- .../Management/20240610184839_V3_2.cs | 4 +- ...ner.cs => 20241202095351_V3_3.Designer.cs} | 2 +- ...9102906_V3_3.cs => 20241202095351_V3_3.cs} | 0 .../Runtime/20231024160952_Initial.cs | 3 +- .../Migrations/Runtime/20240329200711_V3_1.cs | 4 +- .../Migrations/Runtime/20240610184748_V3_2.cs | 4 +- ...ner.cs => 20241202095308_V3_3.Designer.cs} | 2 +- ...9102821_V3_3.cs => 20241202095308_V3_3.cs} | 0 .../Tenants/20241202095647_V3_3.Designer.cs | 57 ++++++++ .../Migrations/Tenants/20241202095647_V3_3.cs | 60 +++++++++ .../TenantsElsaDbContextModelSnapshot.cs | 54 ++++++++ .../DbContextFactories.cs | 24 ++-- .../Alterations/20231015122155_Initial.cs | 3 +- .../Alterations/20240329200626_V3_1.cs | 4 +- .../Alterations/20240610184545_V3_2.cs | 4 +- ...ner.cs => 20241202095036_V3_3.Designer.cs} | 2 +- ...9102610_V3_3.cs => 20241202095036_V3_3.cs} | 0 .../Identity/20231015122242_Initial.cs | 3 +- .../Identity/20240329200753_V3_1.cs | 4 +- .../Identity/20240610184919_V3_2.cs | 4 +- ...ner.cs => 20241202095420_V3_3.Designer.cs} | 2 +- ...9102931_V3_3.cs => 20241202095420_V3_3.cs} | 0 .../Labels/20231015122257_Initial.cs | 3 +- .../Migrations/Labels/20240329200825_V3_1.cs | 4 +- .../Migrations/Labels/20240610185009_V3_2.cs | 4 +- ...ner.cs => 20241202095545_V3_3.Designer.cs} | 2 +- ...9103011_V3_3.cs => 20241202095545_V3_3.cs} | 0 .../Management/20231015122227_Initial.cs | 3 +- .../Management/20240329200727_V3_1.cs | 4 +- .../Management/20240610184814_V3_2.cs | 4 +- ...ner.cs => 20241202095340_V3_3.Designer.cs} | 2 +- ...9102850_V3_3.cs => 20241202095340_V3_3.cs} | 0 .../Runtime/20231024160944_Initial.cs | 3 +- .../Migrations/Runtime/20240329200657_V3_1.cs | 4 +- .../Migrations/Runtime/20240610184719_V3_2.cs | 4 +- ...ner.cs => 20241202095236_V3_3.Designer.cs} | 2 +- ...9102758_V3_3.cs => 20241202095236_V3_3.cs} | 0 .../Tenants/20241202095629_V3_3.Designer.cs | 57 ++++++++ .../Migrations/Tenants/20241202095629_V3_3.cs | 60 +++++++++ .../TenantsElsaDbContextModelSnapshot.cs | 54 ++++++++ .../DbContextFactories.cs | 24 ++-- .../Alterations/20240329200634_V3_1.cs | 4 +- .../Alterations/20240610184608_V3_2.cs | 4 +- ...ner.cs => 20241202095051_V3_3.Designer.cs} | 2 +- ...9102628_V3_3.cs => 20241202095051_V3_3.cs} | 0 .../Identity/20240329200800_V3_1.cs | 4 +- .../Identity/20240610184930_V3_2.cs | 4 +- ...ner.cs => 20241202095439_V3_3.Designer.cs} | 2 +- ...9102941_V3_3.cs => 20241202095439_V3_3.cs} | 0 .../Migrations/Labels/20240329200834_V3_1.cs | 4 +- .../Migrations/Labels/20240610185018_V3_2.cs | 4 +- ...ner.cs => 20241202095558_V3_3.Designer.cs} | 2 +- ...9103018_V3_3.cs => 20241202095558_V3_3.cs} | 0 .../Management/20240329200734_V3_1.cs | 4 +- .../Management/20240610184829_V3_2.cs | 4 +- ...ner.cs => 20241202095346_V3_3.Designer.cs} | 2 +- ...9102858_V3_3.cs => 20241202095346_V3_3.cs} | 0 .../Migrations/Runtime/20240329200705_V3_1.cs | 4 +- .../Migrations/Runtime/20240610184731_V3_2.cs | 4 +- ...ner.cs => 20241202095254_V3_3.Designer.cs} | 2 +- ...9102811_V3_3.cs => 20241202095254_V3_3.cs} | 0 .../Tenants/20241202095639_V3_3.Designer.cs | 51 +++++++ .../Migrations/Tenants/20241202095639_V3_3.cs | 53 ++++++++ .../TenantsElsaDbContextModelSnapshot.cs | 48 +++++++ .../Modules/Identity/UserStore.cs | 18 +-- .../Tenants/ConfigurationJsonConverter.cs | 22 ++++ .../Modules/Tenants/Configurations.cs | 19 +++ .../Modules/Tenants/DbContext.cs | 31 +++++ .../Modules/Tenants/Extensions.cs | 18 +++ .../Modules/Tenants/Feature.cs | 35 +++++ .../Modules/Tenants/TenantStore.cs | 54 ++++++++ .../DefaultHttpEndpointBasePathProvider.cs | 2 +- .../Elsa.Kafka/Activities/ProduceMessage.cs | 2 +- .../Elsa.MongoDb/Common/MongoDbStore.cs | 25 ++-- .../Modules/Tenants/CreateIndices.cs | 38 ++++++ .../Modules/Tenants/Extensions.cs | 19 +++ .../Elsa.MongoDb/Modules/Tenants/Feature.cs | 37 ++++++ .../Modules/Tenants/TenantStore.cs | 69 ++++++++++ .../MySqlQuartzExtensions.cs | 3 +- .../PostgreSqlQuartzExtensions.cs | 1 - .../SqlServerQuartzExtensions.cs | 2 - .../SqliteQuartzExtensions.cs | 1 - .../Extensions/JobExecutionExtensions.cs | 3 + .../Features/QuartzSchedulerFeature.cs | 4 +- .../Elsa.Quartz/Jobs/ResumeWorkflowJob.cs | 34 +++-- .../Elsa.Quartz/Jobs/RunWorkflowJob.cs | 64 +++++---- .../Services/QuartzWorkflowScheduler.cs | 69 ++++++---- .../Features/SchedulingFeature.cs | 4 + .../Handlers/DeleteSchedules.cs | 37 ++---- .../Handlers/UpdateTenantSchedules.cs | 60 +++++++++ ...enantPrefixHttpEndpointBasePathProvider.cs | 2 +- .../Elsa.Tenants/Contracts/ITenantStore.cs | 15 +++ src/modules/Elsa.Tenants/Elsa.Tenants.csproj | 1 + .../Elsa.Tenants.csproj.DotSettings | 3 +- .../Endpoints/Tenants/Add/Endpoint.cs | 39 ++++++ .../Endpoints/Tenants/Delete/Endpoint.cs | 28 ++++ .../Endpoints/Tenants/Get/Endpoint.cs | 28 ++++ .../Endpoints/Tenants/List/Endpoint.cs | 25 ++++ .../Endpoints/Tenants/Refresh/Endpoint.cs | 19 +++ .../Endpoints/Tenants/Update/Endpoint.cs | 43 ++++++ .../Extensions/ModuleExtensions.cs | 18 +++ .../TenantManagementEndpointsFeature.cs | 19 +++ .../Features/TenantManagementFeature.cs | 29 ++++ .../Elsa.Tenants/Features/TenantsFeature.cs | 23 +++- .../Options/MultitenancyOptions.cs | 7 - .../Elsa.Tenants/Options/TenantsOptions.cs | 15 +++ .../Providers/ConfigurationTenantsProvider.cs | 48 +++++-- .../Providers/StoreTenantsProvider.cs | 18 +++ .../DefaultTenantResolverPipelineInvoker.cs | 2 +- .../Services/MemoryTenantStore.cs | 61 +++++++++ .../TenantResolutionPipelineBuilder.cs | 2 +- .../Stores/MemoryWorkflowDefinitionStore.cs | 56 ++++---- .../WorkflowGraphNotFoundException.cs | 8 ++ .../Features/WorkflowRuntimeFeature.cs | 1 - .../Filters/BookmarkFilter.cs | 7 +- .../Filters/TriggerFilter.cs | 7 +- .../Services/DefaultWorkflowStarter.cs | 3 +- 229 files changed, 2718 insertions(+), 695 deletions(-) delete mode 100644 src/modules/Elsa.Common/Features/BackgroundTasksFeature.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantBackgroundService.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantHostedService.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Contracts/IBackgroundTaskStarter.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Contracts/ITaskExecutor.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Contracts/ITenantActivatedEvent.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/Contracts/ITenantContextInitializer.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Contracts/ITenantDeactivatedEvent.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Contracts/ITenantService.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/EventArgs/TenantActivatedEventArgs.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/EventArgs/TenantDeactivatedEventArgs.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/EventArgs/TenantEventArgs.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/EventHandlers/RunBackgroundTasks.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/EventHandlers/RunStartupTasks.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/HostedServices/ActivateTenants.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/HostedServices/BackgroundTasksRunner.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/HostedServices/RecurringTasksRunner.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/HostedServices/StartupTasksRunner.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/HostedServices/TaskExecutor.cs delete mode 100644 src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantContextInitializer.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantService.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Implementations/TaskExecutor.cs create mode 100644 src/modules/Elsa.Common/Multitenancy/Implementations/TenantEventsManager.cs create mode 100644 src/modules/Elsa.Common/RecurringTasks/OrderAttribute.cs create mode 100644 src/modules/Elsa.Common/Serialization/ConfigurationJsonConverter.cs create mode 100644 src/modules/Elsa.Common/Serialization/SerializerOptions.cs create mode 100644 src/modules/Elsa.Common/Serialization/Serializers.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/{20241109102522_V3_3.Designer.cs => 20241202095004_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/{20241109102522_V3_3.cs => 20241202095004_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/{20241109102921_V3_3.Designer.cs => 20241202095402_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/{20241109102921_V3_3.cs => 20241202095402_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/{20241109103004_V3_3.Designer.cs => 20241202095515_V3_3.Designer.cs} (98%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/{20241109103004_V3_3.cs => 20241202095515_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/{20241109102839_V3_3.Designer.cs => 20241202095327_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/{20241109102839_V3_3.cs => 20241202095327_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/{20241109102737_V3_3.Designer.cs => 20241202095210_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/{20241109102737_V3_3.cs => 20241202095210_V3_3.cs} (100%) create mode 100644 src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.Designer.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/{20241109102700_V3_3.Designer.cs => 20241202095130_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/{20241109102700_V3_3.cs => 20241202095130_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/{20241109102957_V3_3.Designer.cs => 20241202095500_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/{20241109102957_V3_3.cs => 20241202095500_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/{20241109103034_V3_3.Designer.cs => 20241202095616_V3_3.Designer.cs} (98%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/{20241109103034_V3_3.cs => 20241202095616_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/{20241109102913_V3_3.Designer.cs => 20241202095357_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/{20241109102913_V3_3.cs => 20241202095357_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/{20241109102830_V3_3.Designer.cs => 20241202095317_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/{20241109102830_V3_3.cs => 20241202095317_V3_3.cs} (100%) create mode 100644 src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.Designer.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/{20241109102646_V3_3.Designer.cs => 20241202095104_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/{20241109102646_V3_3.cs => 20241202095104_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/{20241109102950_V3_3.Designer.cs => 20241202095450_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/{20241109102950_V3_3.cs => 20241202095450_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/{20241109103027_V3_3.Designer.cs => 20241202095605_V3_3.Designer.cs} (98%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/{20241109103027_V3_3.cs => 20241202095605_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/{20241109102906_V3_3.Designer.cs => 20241202095351_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/{20241109102906_V3_3.cs => 20241202095351_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/{20241109102821_V3_3.Designer.cs => 20241202095308_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/{20241109102821_V3_3.cs => 20241202095308_V3_3.cs} (100%) create mode 100644 src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.Designer.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/{20241109102610_V3_3.Designer.cs => 20241202095036_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/{20241109102610_V3_3.cs => 20241202095036_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/{20241109102931_V3_3.Designer.cs => 20241202095420_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/{20241109102931_V3_3.cs => 20241202095420_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/{20241109103011_V3_3.Designer.cs => 20241202095545_V3_3.Designer.cs} (98%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/{20241109103011_V3_3.cs => 20241202095545_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/{20241109102850_V3_3.Designer.cs => 20241202095340_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/{20241109102850_V3_3.cs => 20241202095340_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/{20241109102758_V3_3.Designer.cs => 20241202095236_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/{20241109102758_V3_3.cs => 20241202095236_V3_3.cs} (100%) create mode 100644 src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.Designer.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/{20241109102628_V3_3.Designer.cs => 20241202095051_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/{20241109102628_V3_3.cs => 20241202095051_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/{20241109102941_V3_3.Designer.cs => 20241202095439_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/{20241109102941_V3_3.cs => 20241202095439_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/{20241109103018_V3_3.Designer.cs => 20241202095558_V3_3.Designer.cs} (98%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/{20241109103018_V3_3.cs => 20241202095558_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/{20241109102858_V3_3.Designer.cs => 20241202095346_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/{20241109102858_V3_3.cs => 20241202095346_V3_3.cs} (100%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/{20241109102811_V3_3.Designer.cs => 20241202095254_V3_3.Designer.cs} (99%) rename src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/{20241109102811_V3_3.cs => 20241202095254_V3_3.cs} (100%) create mode 100644 src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.Designer.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/ConfigurationJsonConverter.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Configurations.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Extensions.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Feature.cs create mode 100644 src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/TenantStore.cs create mode 100644 src/modules/Elsa.MongoDb/Modules/Tenants/CreateIndices.cs create mode 100644 src/modules/Elsa.MongoDb/Modules/Tenants/Extensions.cs create mode 100644 src/modules/Elsa.MongoDb/Modules/Tenants/Feature.cs create mode 100644 src/modules/Elsa.MongoDb/Modules/Tenants/TenantStore.cs create mode 100644 src/modules/Elsa.Scheduling/Handlers/UpdateTenantSchedules.cs create mode 100644 src/modules/Elsa.Tenants/Contracts/ITenantStore.cs create mode 100644 src/modules/Elsa.Tenants/Endpoints/Tenants/Add/Endpoint.cs create mode 100644 src/modules/Elsa.Tenants/Endpoints/Tenants/Delete/Endpoint.cs create mode 100644 src/modules/Elsa.Tenants/Endpoints/Tenants/Get/Endpoint.cs create mode 100644 src/modules/Elsa.Tenants/Endpoints/Tenants/List/Endpoint.cs create mode 100644 src/modules/Elsa.Tenants/Endpoints/Tenants/Refresh/Endpoint.cs create mode 100644 src/modules/Elsa.Tenants/Endpoints/Tenants/Update/Endpoint.cs create mode 100644 src/modules/Elsa.Tenants/Features/TenantManagementEndpointsFeature.cs create mode 100644 src/modules/Elsa.Tenants/Features/TenantManagementFeature.cs create mode 100644 src/modules/Elsa.Tenants/Options/TenantsOptions.cs create mode 100644 src/modules/Elsa.Tenants/Providers/StoreTenantsProvider.cs create mode 100644 src/modules/Elsa.Tenants/Services/MemoryTenantStore.cs create mode 100644 src/modules/Elsa.Workflows.Runtime/Exceptions/WorkflowGraphNotFoundException.cs diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 3a77d06f0..820ac8cda 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -5,7 +5,7 @@ on: branches: - 'main' release: - types: [ prereleased, published ] + types: [ prereleased ] env: base_version: '3.3.0' feedz_feed_source: 'https://f.feedz.io/elsa-workflows/elsa-3/nuget/index.json' diff --git a/Directory.Packages.props b/Directory.Packages.props index 0395ab99e..157742de1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -131,6 +131,7 @@ + @@ -167,6 +168,7 @@ + diff --git a/scripts/migrations/efcore-3.3.sh b/scripts/migrations/efcore-3.3.sh index 9d6198511..a285230b8 100755 --- a/scripts/migrations/efcore-3.3.sh +++ b/scripts/migrations/efcore-3.3.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Define the modules to update -mods=("Alterations" "Runtime" "Management" "Identity" "Labels") +mods=("Alterations" "Runtime" "Management" "Identity" "Labels" "Tenants") # Define the list of providers providers=("MySql" "SqlServer" "Sqlite" "PostgreSql" "Oracle") diff --git a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj index 0b5e96890..67c92b74c 100644 --- a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj +++ b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj @@ -63,8 +63,4 @@ - - - - diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index 353359bf6..38df82113 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -13,6 +13,7 @@ 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.Extensions; using Elsa.Features.Services; using Elsa.Identity.Multitenancy; @@ -24,6 +25,7 @@ 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.Secrets.Extensions; using Elsa.Secrets.Management.Tasks; @@ -73,6 +75,7 @@ const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed; const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.MassTransit; const MassTransitBroker massTransitBroker = MassTransitBroker.Memory; const bool useMultitenancy = false; +const bool useTenantsFromConfiguration = false; const bool useAgents = false; const bool useSecrets = false; const bool disableVariableWrappers = false; @@ -146,7 +149,7 @@ services ef.UseSqlServer(sqlServerConnectionString!); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString!); - else if(sqlDatabaseProvider == SqlDatabaseProvider.MySql) + else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) ef.UseMySql(mySqlConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString!); @@ -180,7 +183,7 @@ services ef.UseSqlServer(sqlServerConnectionString!); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString!); - else if(sqlDatabaseProvider == SqlDatabaseProvider.MySql) + else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) ef.UseMySql(mySqlConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString!); @@ -251,7 +254,7 @@ services } else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString!); - else if(sqlDatabaseProvider == SqlDatabaseProvider.MySql) + else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) ef.UseMySql(mySqlConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString!); @@ -376,7 +379,7 @@ services ef.UseSqlServer(sqlServerConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString); - else if(sqlDatabaseProvider == SqlDatabaseProvider.MySql) + else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) ef.UseMySql(mySqlConnectionString); else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString!); @@ -452,8 +455,8 @@ services asb.AzureServiceBusOptions = options => configuration.GetSection("AzureServiceBus").Bind(options); }); } - - if(useKafka) + + if (useKafka) { elsa.UseKafka(kafka => { @@ -468,10 +471,10 @@ services .UseAgentPersistence(persistence => persistence.UseEntityFrameworkCore(ef => ef.UseSqlite(sp => sp.GetSqliteConnectionString()))) .UseAgentsApi() ; - + services.Configure(options => builder.Configuration.GetSection("Agents").Bind(options)); } - + if (useSecrets) { elsa @@ -482,11 +485,11 @@ services if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) management.UseEntityFrameworkCore(ef => ef.UseSqlServer(sqlServerConnectionString) - ); + ); else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) - management.UseEntityFrameworkCore(ef => + management.UseEntityFrameworkCore(ef => ef.UsePostgreSql(postgresConnectionString) - ); + ); else management.UseEntityFrameworkCore(ef => { @@ -502,16 +505,44 @@ services { elsa.UseTenants(tenants => { - tenants.ConfigureOptions(options => + tenants.ConfigureMultitenancy(options => { - configuration.GetSection("Multitenancy").Bind(options); options.TenantResolverPipelineBuilder .Append() .Append() .Append() .Append(); }); - tenants.UseConfigurationBasedTenantsProvider(); + + 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 => + { + if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) ef.UseSqlite(sqliteConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) ef.UseMySql(mySqlConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString); + }); + } + }); + + tenants.UseTenantManagementEndpoints(); + } }); elsa.UseTenantHttpRouting(); @@ -562,7 +593,7 @@ app.UseAuthentication(); app.UseAuthorization(); // Multitenancy. -if(useMultitenancy) +if (useMultitenancy) app.UseTenants(); // Elsa API endpoints for designer. diff --git a/src/apps/Elsa.Server.Web/appsettings.json b/src/apps/Elsa.Server.Web/appsettings.json index 13b84c684..889afc59e 100644 --- a/src/apps/Elsa.Server.Web/appsettings.json +++ b/src/apps/Elsa.Server.Web/appsettings.json @@ -12,6 +12,9 @@ "System.Net.Http": "Warning" } }, + "HostBuilder": { + "reloadConfigOnChange": true + }, "AllowedHosts": "*", "ConnectionStrings": { "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;", @@ -37,6 +40,19 @@ "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;" } } + }, + { + "Id": "tenant-1", + "Name": "Tenant 12 dd", + "Configuration": { + "Http": { + "Prefix": "/tenant-1", + "Host": "localhost:5001" + }, + "ConnectionStrings": { + "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;" + } + } } ] }, @@ -81,7 +97,7 @@ "Roles": [ "admin" ], - "TenantId": null + "TenantId": "" }, { "Id": "b0cd0e506e713a9d", diff --git a/src/common/Elsa.Testing.Shared.Component/Services/TestTenantsProvider.cs b/src/common/Elsa.Testing.Shared.Component/Services/TestTenantsProvider.cs index 0059cbc3a..8dffc68fa 100644 --- a/src/common/Elsa.Testing.Shared.Component/Services/TestTenantsProvider.cs +++ b/src/common/Elsa.Testing.Shared.Component/Services/TestTenantsProvider.cs @@ -1,17 +1,18 @@ using Elsa.Common.Multitenancy; -using Elsa.Tenants; +using JetBrains.Annotations; namespace Elsa.Testing.Shared.Services; +[UsedImplicitly] public class TestTenantsProvider(params string[] tenantIds) : ITenantsProvider { - public ValueTask> ListAsync(CancellationToken cancellationToken = default) + public Task> ListAsync(CancellationToken cancellationToken = default) { var tenants = tenantIds.Select(id => new Tenant {Id = id, Name = id}); - return new(tenants); + return Task.FromResult(tenants); } - public async ValueTask FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + public async Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) { var query = (await ListAsync(cancellationToken)).AsQueryable(); return filter.Apply(query).FirstOrDefault(); diff --git a/src/modules/Elsa.Common/Elsa.Common.csproj b/src/modules/Elsa.Common/Elsa.Common.csproj index 105f9b8e5..7899635c9 100644 --- a/src/modules/Elsa.Common/Elsa.Common.csproj +++ b/src/modules/Elsa.Common/Elsa.Common.csproj @@ -18,6 +18,7 @@ + diff --git a/src/modules/Elsa.Common/Elsa.Common.csproj.DotSettings b/src/modules/Elsa.Common/Elsa.Common.csproj.DotSettings index 4a6456d86..298209647 100644 --- a/src/modules/Elsa.Common/Elsa.Common.csproj.DotSettings +++ b/src/modules/Elsa.Common/Elsa.Common.csproj.DotSettings @@ -7,7 +7,9 @@ True True True + True True True True + True True \ No newline at end of file diff --git a/src/modules/Elsa.Common/Features/BackgroundTasksFeature.cs b/src/modules/Elsa.Common/Features/BackgroundTasksFeature.cs deleted file mode 100644 index 400d6a144..000000000 --- a/src/modules/Elsa.Common/Features/BackgroundTasksFeature.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Elsa.Common.Multitenancy.HostedServices; -using Elsa.Common.RecurringTasks; -using Elsa.Extensions; -using Elsa.Features.Abstractions; -using Elsa.Features.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace Elsa.Common.Features; - -public class BackgroundTasksFeature(IModule module) : FeatureBase(module) -{ - public override void Apply() - { - Services.AddSingleton(); - Services.AddScoped(); - Services.AddStartupTask(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Features/MultitenancyFeature.cs b/src/modules/Elsa.Common/Features/MultitenancyFeature.cs index 6c334c6d9..84facff7b 100644 --- a/src/modules/Elsa.Common/Features/MultitenancyFeature.cs +++ b/src/modules/Elsa.Common/Features/MultitenancyFeature.cs @@ -1,5 +1,8 @@ using Elsa.Common.Multitenancy; +using Elsa.Common.Multitenancy.EventHandlers; using Elsa.Common.Multitenancy.HostedServices; +using Elsa.Common.RecurringTasks; +using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Services; using Microsoft.Extensions.DependencyInjection; @@ -26,9 +29,7 @@ public class MultitenancyFeature(IModule module) : FeatureBase(module) public override void ConfigureHostedServices() { Module - .ConfigureHostedService(1) - .ConfigureHostedService(1) - .ConfigureHostedService(1) + .ConfigureHostedService(-1) ; } @@ -38,9 +39,24 @@ public class MultitenancyFeature(IModule module) : FeatureBase(module) .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() + + .AddSingleton() + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton(sp => sp.GetRequiredService()) + + .AddSingleton() + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton(sp => sp.GetRequiredService()) + + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddStartupTask() .AddScoped() .AddScoped() + .AddScoped() + .AddScoped() .AddScoped(_tenantsProviderFactory); } } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantBackgroundService.cs b/src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantBackgroundService.cs deleted file mode 100644 index bafd2c14e..000000000 --- a/src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantBackgroundService.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Elsa.Common.Multitenancy; - -/// -/// A hosted service that executes a task for each tenant. -/// -public abstract class MultitenantBackgroundService(IServiceScopeFactory serviceScopeFactory) : BackgroundService -{ - private bool _disposed; - protected IServiceScope ServiceScope { get; private set; } = default!; - protected ICollection Tenants { get; private set; } = default!; - protected IDictionary TenantScopes { get; private set; } = default!; - - public override async Task StartAsync(CancellationToken cancellationToken) - { - ServiceScope = serviceScopeFactory.CreateScope(); - var tenantScopeFactory = ServiceScope.ServiceProvider.GetRequiredService(); - var tenantsProvider = ServiceScope.ServiceProvider.GetRequiredService(); - Tenants = (await tenantsProvider.ListAsync(cancellationToken)).ToList(); - TenantScopes = Tenants.ToDictionary(x => x, x => tenantScopeFactory.CreateScope(x)); - - foreach (var entry in TenantScopes) - await StartAsync(entry.Value, cancellationToken); - - await base.StartAsync(cancellationToken); - } - - public override async Task StopAsync(CancellationToken cancellationToken) - { - if(_disposed) - return; - - foreach (var entry in TenantScopes) - await StopAsync(entry.Value, cancellationToken); - - foreach (var entry in TenantScopes) - await entry.Value.DisposeAsync(); - - ServiceScope.Dispose(); - await base.StopAsync(cancellationToken); - - _disposed = true; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - foreach (var entry in TenantScopes) - await ExecuteAsync(entry.Value, stoppingToken); - } - - protected virtual Task StartAsync(TenantScope tenantScope, CancellationToken stoppingToken) => Task.CompletedTask; - protected virtual Task ExecuteAsync(TenantScope tenantScope, CancellationToken stoppingToken) => Task.CompletedTask; - protected virtual Task StopAsync(TenantScope tenantScope, CancellationToken stoppingToken) => Task.CompletedTask; -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantHostedService.cs b/src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantHostedService.cs deleted file mode 100644 index 845685ae0..000000000 --- a/src/modules/Elsa.Common/Multitenancy/Abstractions/MultitenantHostedService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Elsa.Common.Multitenancy; - -/// -/// A hosted service that runs the start and stop methods for each tenant. -/// -[UsedImplicitly] -public abstract class MultitenantHostedService(IServiceScopeFactory serviceScopeFactory) : IHostedService -{ - public virtual async Task StartAsync(CancellationToken cancellationToken) => await RunThroughTenantsAsync(StartAsync, cancellationToken); - public virtual Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - protected virtual Task StartAsync(TenantScope tenantScope, CancellationToken cancellationToken) => Task.CompletedTask; - - private async Task RunThroughTenantsAsync(Func action, CancellationToken cancellationToken) - { - using var serviceScope = serviceScopeFactory.CreateScope(); - var tenantScopeFactory = serviceScope.ServiceProvider.GetRequiredService(); - var tenantsProvider = serviceScope.ServiceProvider.GetRequiredService(); - var tenants = (await tenantsProvider.ListAsync(cancellationToken)).ToList(); - - foreach (var tenant in tenants) - { - await using var tenantScope = tenantScopeFactory.CreateScope(tenant); - await action(tenantScope, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Abstractions/TenantResolverBase.cs b/src/modules/Elsa.Common/Multitenancy/Abstractions/TenantResolverBase.cs index 01adf6422..db0966a5d 100644 --- a/src/modules/Elsa.Common/Multitenancy/Abstractions/TenantResolverBase.cs +++ b/src/modules/Elsa.Common/Multitenancy/Abstractions/TenantResolverBase.cs @@ -40,9 +40,4 @@ public abstract class TenantResolverBase : ITenantResolver /// Automatically resolves the tenant if the tenant ID is not null. /// protected TenantResolverResult AutoResolve(string? tenantId) => tenantId == null ? Unresolved() : Resolved(tenantId); - - public Task ResolveAsync(CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/IBackgroundTaskStarter.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/IBackgroundTaskStarter.cs new file mode 100644 index 000000000..4aaae4ebd --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/IBackgroundTaskStarter.cs @@ -0,0 +1,7 @@ +namespace Elsa.Common.Multitenancy; + +public interface IBackgroundTaskStarter +{ + Task StartAsync(IBackgroundTask task, CancellationToken cancellationToken); + Task StopAsync(IBackgroundTask task, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITaskExecutor.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITaskExecutor.cs new file mode 100644 index 000000000..3694f7f3d --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/ITaskExecutor.cs @@ -0,0 +1,6 @@ +namespace Elsa.Common.Multitenancy; + +public interface ITaskExecutor +{ + Task ExecuteTaskAsync(ITask task, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantAccessor.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantAccessor.cs index 09355ef5c..66dab5b99 100644 --- a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantAccessor.cs +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantAccessor.cs @@ -6,5 +6,7 @@ public interface ITenantAccessor /// Get the current . /// /// Current tenant or null. - Tenant? Tenant { get; set; } + Tenant? Tenant { get; } + + IDisposable PushContext(Tenant? tenant); } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantActivatedEvent.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantActivatedEvent.cs new file mode 100644 index 000000000..8bba0657e --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantActivatedEvent.cs @@ -0,0 +1,6 @@ +namespace Elsa.Common.Multitenancy; + +public interface ITenantActivatedEvent +{ + Task TenantActivatedAsync(TenantActivatedEventArgs args); +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantContextInitializer.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantContextInitializer.cs deleted file mode 100644 index c989076d4..000000000 --- a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantContextInitializer.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Elsa.Common.Multitenancy; - -public interface ITenantContextInitializer -{ - Task InitializeAsync(string tenantId, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantDeactivatedEvent.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantDeactivatedEvent.cs new file mode 100644 index 000000000..d1761057e --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantDeactivatedEvent.cs @@ -0,0 +1,6 @@ +namespace Elsa.Common.Multitenancy; + +public interface ITenantDeactivatedEvent +{ + Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args); +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantService.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantService.cs new file mode 100644 index 000000000..88b1e44fb --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantService.cs @@ -0,0 +1,68 @@ +namespace Elsa.Common.Multitenancy; + +public interface ITenantService +{ + /// + /// Finds a tenant by the given id. + /// + /// The tenant id. + /// The cancellation token. + /// The tenant or null if not found. + Task FindAsync(string id, CancellationToken cancellationToken = default); + + /// + /// Finds a tenant using the given filter. + /// + /// The filter. + /// The cancellation token. + Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default); + + /// + /// Gets a tenant by the given id. + /// + /// The tenant id. + /// The cancellation token. + /// The tenant. + /// Thrown if the tenant is not found. + Task GetAsync(string id, CancellationToken cancellationToken = default); + + /// + /// Gets a tenant by the given id. + /// + /// The filter. + /// The cancellation token. + /// The tenant. + /// Thrown if the tenant is not found. + Task GetAsync(TenantFilter filter, CancellationToken cancellationToken = default); + + /// + /// Lists all the tenants. + /// + /// The cancellation token. + /// The tenants. + Task> ListAsync(CancellationToken cancellationToken = default); + + /// + /// Lists all the tenants using the given filter. + /// + /// The filter. + /// The cancellation token. + /// The tenants. + Task> ListAsync(TenantFilter filter, CancellationToken cancellationToken = default); + + /// + /// Activates all tenants. + /// + Task ActivateTenantsAsync(CancellationToken cancellationToken = default); + + /// + /// Deactivates all tenants. + /// + Task DeactivateTenantsAsync(CancellationToken cancellationToken = default); + + /// + /// Invokes the and caches the result. + /// When new tenants are added, lifecycle events are triggered to ensure background tasks are updated. + /// + Task RefreshAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantsProvider.cs b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantsProvider.cs index 624945fa0..e5e7a494f 100644 --- a/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantsProvider.cs +++ b/src/modules/Elsa.Common/Multitenancy/Contracts/ITenantsProvider.cs @@ -8,10 +8,10 @@ public interface ITenantsProvider /// /// Lists all the tenants. /// - ValueTask> ListAsync(CancellationToken cancellationToken = default); + Task> ListAsync(CancellationToken cancellationToken = default); /// /// Finds a tenant by the given filter. /// - ValueTask FindAsync(TenantFilter filter, CancellationToken cancellationToken = default); + Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Entities/Tenant.cs b/src/modules/Elsa.Common/Multitenancy/Entities/Tenant.cs index 03ebd398a..a832baa2c 100644 --- a/src/modules/Elsa.Common/Multitenancy/Entities/Tenant.cs +++ b/src/modules/Elsa.Common/Multitenancy/Entities/Tenant.cs @@ -22,7 +22,7 @@ public class Tenant : Entity public static readonly Tenant Default = new() { - Id = null!, + Id = string.Empty, Name = "Default" }; } diff --git a/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantActivatedEventArgs.cs b/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantActivatedEventArgs.cs new file mode 100644 index 000000000..a30edf8a2 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantActivatedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Elsa.Common.Multitenancy; + +public record TenantActivatedEventArgs(Tenant Tenant, TenantScope TenantScope, CancellationToken CancellationToken) : TenantEventArgs(Tenant, TenantScope, CancellationToken); \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantDeactivatedEventArgs.cs b/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantDeactivatedEventArgs.cs new file mode 100644 index 000000000..b6cb61ed5 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantDeactivatedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Elsa.Common.Multitenancy; + +public record TenantDeactivatedEventArgs(Tenant Tenant, TenantScope TenantScope, CancellationToken CancellationToken) : TenantEventArgs(Tenant, TenantScope, CancellationToken); \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantEventArgs.cs b/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantEventArgs.cs new file mode 100644 index 000000000..f2813c12c --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/EventArgs/TenantEventArgs.cs @@ -0,0 +1,3 @@ +namespace Elsa.Common.Multitenancy; + +public record TenantEventArgs(Tenant Tenant, TenantScope TenantScope, CancellationToken CancellationToken); \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/EventHandlers/RunBackgroundTasks.cs b/src/modules/Elsa.Common/Multitenancy/EventHandlers/RunBackgroundTasks.cs new file mode 100644 index 000000000..33f684d6d --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/EventHandlers/RunBackgroundTasks.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Common.Multitenancy.EventHandlers; + +public class RunBackgroundTasks : ITenantActivatedEvent, ITenantDeactivatedEvent +{ + private readonly ICollection _runningTasks = new List(); + private CancellationTokenSource _cancellationTokenSource = default!; + + public Task TenantActivatedAsync(TenantActivatedEventArgs args) + { + var cancellationToken = args.CancellationToken; + _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + var tenantScope = args.TenantScope; + var backgroundTasks = tenantScope.ServiceProvider.GetServices(); + var backgroundTaskStarter = tenantScope.ServiceProvider.GetRequiredService(); + + foreach (var backgroundTask in backgroundTasks) + { + var task = backgroundTaskStarter.StartAsync(backgroundTask, _cancellationTokenSource.Token); + if (!task.IsCompleted) _runningTasks.Add(task); + } + + return Task.CompletedTask; + } + + public Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args) + { + _cancellationTokenSource.Cancel(); + _runningTasks.Clear(); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/EventHandlers/RunStartupTasks.cs b/src/modules/Elsa.Common/Multitenancy/EventHandlers/RunStartupTasks.cs new file mode 100644 index 000000000..0bcca5c03 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/EventHandlers/RunStartupTasks.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using Elsa.Common.RecurringTasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Common.Multitenancy.EventHandlers; + +public class RunStartupTasks : ITenantActivatedEvent +{ + public async Task TenantActivatedAsync(TenantActivatedEventArgs args) + { + var cancellationToken = args.CancellationToken; + var tenantScope = args.TenantScope; + var tasks = tenantScope.ServiceProvider.GetServices().OrderBy(x => x.GetType().GetCustomAttribute()?.Order ?? 0f).ToList(); + var taskExecutor = tenantScope.ServiceProvider.GetRequiredService(); + + foreach (var task in tasks) + await taskExecutor.ExecuteTaskAsync(task, cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs b/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs new file mode 100644 index 000000000..c89c79c70 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs @@ -0,0 +1,49 @@ +using Elsa.Common.RecurringTasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Elsa.Common.Multitenancy.EventHandlers; + +public class StartRecurringTasks(RecurringTaskScheduleManager scheduleManager, ILogger logger) : ITenantActivatedEvent, ITenantDeactivatedEvent +{ + private readonly ICollection _scheduledTimers = new List(); + private CancellationTokenSource _cancellationTokenSource = default!; + + public async Task TenantActivatedAsync(TenantActivatedEventArgs args) + { + var cancellationToken = args.CancellationToken; + _cancellationTokenSource = new CancellationTokenSource(); + var tenantScope = args.TenantScope; + var tasks = tenantScope.ServiceProvider.GetServices().ToList(); + var taskExecutor = tenantScope.ServiceProvider.GetRequiredService(); + + foreach (var task in tasks) + { + var schedule = scheduleManager.GetScheduleFor(task.GetType()); + var timer = schedule.CreateTimer(async () => + { + try + { + await taskExecutor.ExecuteTaskAsync(task, _cancellationTokenSource.Token); + } + catch (OperationCanceledException e) + { + logger.LogInformation(e, "Task {TaskType} was cancelled", task.GetType().Name); + } + + }); + _scheduledTimers.Add(timer); + await task.StartAsync(cancellationToken); + } + } + + public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args) + { + var tenantScope = args.TenantScope; + _cancellationTokenSource.Cancel(); + foreach (var timer in _scheduledTimers) await timer.DisposeAsync(); + _scheduledTimers.Clear(); + var tasks = tenantScope.ServiceProvider.GetServices(); + foreach (var task in tasks) await task.StopAsync(args.CancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/HostedServices/ActivateTenants.cs b/src/modules/Elsa.Common/Multitenancy/HostedServices/ActivateTenants.cs new file mode 100644 index 000000000..bb5fed0f3 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/HostedServices/ActivateTenants.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.Hosting; + +namespace Elsa.Common.Multitenancy.HostedServices; + +[UsedImplicitly] +public class ActivateTenants(ITenantService tenantService) : IHostedService +{ + public async Task StartAsync(CancellationToken cancellationToken) + { + await tenantService.ActivateTenantsAsync(cancellationToken); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await tenantService.DeactivateTenantsAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/HostedServices/BackgroundTasksRunner.cs b/src/modules/Elsa.Common/Multitenancy/HostedServices/BackgroundTasksRunner.cs deleted file mode 100644 index 490682f1a..000000000 --- a/src/modules/Elsa.Common/Multitenancy/HostedServices/BackgroundTasksRunner.cs +++ /dev/null @@ -1,27 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; - -namespace Elsa.Common.Multitenancy.HostedServices; - -[UsedImplicitly] -public class BackgroundTasksRunner(IServiceScopeFactory serviceScopeFactory) : MultitenantBackgroundService(serviceScopeFactory) -{ - protected override async Task StartAsync(TenantScope tenantScope, CancellationToken stoppingToken) - { - var tasks = tenantScope.ServiceProvider.GetServices(); - foreach (var task in tasks) await task.StartAsync(stoppingToken); - } - - protected override async Task ExecuteAsync(TenantScope tenantScope, CancellationToken stoppingToken) - { - var tasks = tenantScope.ServiceProvider.GetServices(); - var taskExecutor = tenantScope.ServiceProvider.GetRequiredService(); - foreach (var task in tasks) await taskExecutor.ExecuteTaskAsync(task, stoppingToken); - } - - protected override async Task StopAsync(TenantScope tenantScope, CancellationToken stoppingToken) - { - var tasks = tenantScope.ServiceProvider.GetServices(); - foreach (var task in tasks) await task.StopAsync(stoppingToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/HostedServices/RecurringTasksRunner.cs b/src/modules/Elsa.Common/Multitenancy/HostedServices/RecurringTasksRunner.cs deleted file mode 100644 index b18c7ef40..000000000 --- a/src/modules/Elsa.Common/Multitenancy/HostedServices/RecurringTasksRunner.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Elsa.Common.RecurringTasks; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; - -namespace Elsa.Common.Multitenancy.HostedServices; - -[UsedImplicitly] -public class RecurringTasksRunner(IServiceScopeFactory serviceScopeFactory, RecurringTaskScheduleManager scheduleManager) : MultitenantBackgroundService(serviceScopeFactory) -{ - private readonly ICollection _scheduledTimers = new List(); - - protected override async Task StartAsync(TenantScope tenantScope, CancellationToken stoppingToken) - { - var tasks = tenantScope.ServiceProvider.GetServices().ToList(); - var taskExecutor = tenantScope.ServiceProvider.GetRequiredService(); - - foreach (var task in tasks) - { - var schedule = scheduleManager.GetScheduleFor(task.GetType()); - var timer = schedule.CreateTimer(async () => - { - await taskExecutor.ExecuteTaskAsync(task, stoppingToken); - }); - _scheduledTimers.Add(timer); - await task.StartAsync(stoppingToken); - } - } - - protected override async Task StopAsync(TenantScope tenantScope, CancellationToken stoppingToken) - { - foreach (var timer in _scheduledTimers) await timer.DisposeAsync(); - _scheduledTimers.Clear(); - var tasks = tenantScope.ServiceProvider.GetServices(); - foreach (var task in tasks) await task.StopAsync(stoppingToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/HostedServices/StartupTasksRunner.cs b/src/modules/Elsa.Common/Multitenancy/HostedServices/StartupTasksRunner.cs deleted file mode 100644 index 69c088396..000000000 --- a/src/modules/Elsa.Common/Multitenancy/HostedServices/StartupTasksRunner.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Elsa.Common.DistributedHosting; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Elsa.Common.Multitenancy.HostedServices; - -[UsedImplicitly] -public class StartupTasksRunner(IServiceScopeFactory serviceScopeFactory, IOptions options) : MultitenantHostedService(serviceScopeFactory) -{ - protected override async Task StartAsync(TenantScope tenantScope, CancellationToken cancellationToken) - { - var tasks = tenantScope.ServiceProvider.GetServices(); - var taskExecutor = tenantScope.ServiceProvider.GetRequiredService(); - foreach (var task in tasks) - await taskExecutor.ExecuteTaskAsync(task, cancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/HostedServices/TaskExecutor.cs b/src/modules/Elsa.Common/Multitenancy/HostedServices/TaskExecutor.cs deleted file mode 100644 index 74ffc08dc..000000000 --- a/src/modules/Elsa.Common/Multitenancy/HostedServices/TaskExecutor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Reflection; -using Elsa.Common.DistributedHosting; -using Elsa.Common.RecurringTasks; -using Medallion.Threading; -using Microsoft.Extensions.Options; - -namespace Elsa.Common.Multitenancy.HostedServices; - -public class TaskExecutor(IDistributedLockProvider distributedLockProvider, IOptions options) -{ - public async Task ExecuteTaskAsync(ITask task, CancellationToken cancellationToken) - { - var taskType = task.GetType(); - var singleNodeTask = taskType.GetCustomAttribute() != null; - - if (singleNodeTask) - { - var resourceName = taskType.AssemblyQualifiedName!; - await using (await distributedLockProvider.AcquireLockAsync(resourceName, options.Value.LockAcquisitionTimeout, cancellationToken: cancellationToken)) - await task.ExecuteAsync(cancellationToken); - } - else - { - await task.ExecuteAsync(cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs index 7325c664c..fd8a699ba 100644 --- a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs @@ -11,6 +11,29 @@ public class DefaultTenantAccessor : ITenantAccessor public Tenant? Tenant { get => CurrentTenantField.Value; - set => CurrentTenantField.Value = value; + internal set => CurrentTenantField.Value = value; + } + + public IDisposable PushContext(Tenant? tenant) + { + return new CurrentTenantScope(tenant, Tenant, t => Tenant = t); + } +} + +public class CurrentTenantScope : IDisposable +{ + private readonly Tenant? _previousTenant; + private readonly Action _apply; + + public CurrentTenantScope(Tenant? newTenant, Tenant? previousTenant, Action apply) + { + _previousTenant = previousTenant; + _apply = apply; + _apply(newTenant); + } + + public void Dispose() + { + _apply(_previousTenant); } } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantContextInitializer.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantContextInitializer.cs deleted file mode 100644 index 0b3b929eb..000000000 --- a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantContextInitializer.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Elsa.Common.Multitenancy; - -public class DefaultTenantContextInitializer(ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) : ITenantContextInitializer -{ - public async Task InitializeAsync(string tenantId, CancellationToken cancellationToken = default) - { - var tenant = await tenantFinder.FindByIdAsync(tenantId, cancellationToken); - tenantAccessor.Tenant = tenant; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantResolver.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantResolver.cs index 890375d5a..112aa9f2a 100644 --- a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantResolver.cs +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantResolver.cs @@ -7,7 +7,7 @@ public class DefaultTenantResolver : TenantResolverBase { private readonly Tenant _defaultTenant = new() { - Id = null!, + Id = string.Empty, Name = "Default" }; diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantService.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantService.cs new file mode 100644 index 000000000..9db13dc36 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantService.cs @@ -0,0 +1,124 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Common.Multitenancy; + +public class DefaultTenantService(IServiceScopeFactory scopeFactory, ITenantScopeFactory tenantScopeFactory, TenantEventsManager tenantEvents, ITenantAccessor tenantAccessor) : ITenantService, IAsyncDisposable +{ + private readonly AsyncServiceScope _serviceScope = scopeFactory.CreateAsyncScope(); + private IDictionary? _tenantsDictionary; + private IDictionary? _tenantScopesDictionary; + + public async ValueTask DisposeAsync() + { + await _serviceScope.DisposeAsync(); + } + + public async Task FindAsync(string id, CancellationToken cancellationToken = default) + { + var dictionary = await GetTenantsDictionaryAsync(cancellationToken); + return dictionary.TryGetValue(id, out var tenant) ? tenant : null; + } + + public async Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + var dictionary = await GetTenantsDictionaryAsync(cancellationToken); + return filter.Apply(dictionary.Values.AsQueryable()).FirstOrDefault(); + } + + public async Task GetAsync(string id, CancellationToken cancellationToken = default) + { + var dictionary = await GetTenantsDictionaryAsync(cancellationToken); + return dictionary[id]; + } + + public async Task GetAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + var dictionary = await GetTenantsDictionaryAsync(cancellationToken); + return filter.Apply(dictionary.Values.AsQueryable()).First(); + } + + public async Task> ListAsync(CancellationToken cancellationToken = default) + { + var dictionary = await GetTenantsDictionaryAsync(cancellationToken); + return dictionary.Values; + } + + public async Task> ListAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + var dictionary = await GetTenantsDictionaryAsync(cancellationToken); + return filter.Apply(dictionary.Values.AsQueryable()); + } + + public async Task ActivateTenantsAsync(CancellationToken cancellationToken = default) + { + await RefreshAsync(cancellationToken); + } + + public async Task DeactivateTenantsAsync(CancellationToken cancellationToken = default) + { + var tenants = _tenantsDictionary!.Values.ToArray(); + + foreach (var tenant in tenants) + await UnregisterTenantAsync(tenant, cancellationToken); + } + + public async Task RefreshAsync(CancellationToken cancellationToken = default) + { + await using var scope = scopeFactory.CreateAsyncScope(); + var tenantsProvider = scope.ServiceProvider.GetRequiredService(); + var currentTenants = await GetTenantsDictionaryAsync(cancellationToken); + var currentTenantIds = currentTenants.Keys; + var newTenants = (await tenantsProvider.ListAsync(cancellationToken)).ToDictionary(x => x.Id); + var newTenantIds = newTenants.Keys; + var removedTenantIds = currentTenantIds.Except(newTenantIds).ToArray(); + var addedTenantIds = newTenantIds.Except(currentTenantIds).ToArray(); + + foreach (var removedTenantId in removedTenantIds) + { + var removedTenant = currentTenants[removedTenantId]; + await UnregisterTenantAsync(removedTenant, cancellationToken); + } + + foreach (var addedTenantId in addedTenantIds) + { + var addedTenant = newTenants[addedTenantId]; + await RegisterTenantAsync(addedTenant, cancellationToken); + } + } + + private async Task> GetTenantsDictionaryAsync(CancellationToken cancellationToken) + { + if (_tenantsDictionary == null) + { + _tenantsDictionary = new Dictionary(); + _tenantScopesDictionary = new Dictionary(); + var tenantsProvider = _serviceScope.ServiceProvider.GetRequiredService(); + var tenants = await tenantsProvider.ListAsync(cancellationToken); + + foreach (var tenant in tenants) + await RegisterTenantAsync(tenant, cancellationToken); + } + + return _tenantsDictionary; + } + + private async Task RegisterTenantAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + var scope = tenantScopeFactory.CreateScope(tenant); + _tenantsDictionary![tenant.Id] = tenant; + _tenantScopesDictionary![tenant] = scope; + + using (tenantAccessor.PushContext(tenant)) + await tenantEvents.TenantActivatedAsync(new TenantActivatedEventArgs(tenant, scope, cancellationToken)); + } + + private async Task UnregisterTenantAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + var scope = _tenantScopesDictionary![tenant]; + _tenantsDictionary!.Remove(tenant.Id); + _tenantScopesDictionary!.Remove(tenant); + + using (tenantAccessor.PushContext(tenant)) + await tenantEvents.TenantDeactivatedAsync(new TenantDeactivatedEventArgs(tenant, scope, cancellationToken)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantsProvider.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantsProvider.cs index bc9fd2d61..7618205a5 100644 --- a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantsProvider.cs +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantsProvider.cs @@ -10,14 +10,14 @@ public class DefaultTenantsProvider : ITenantsProvider { private readonly Tenant[] _tenants = { Tenant.Default }; - public ValueTask> ListAsync(CancellationToken cancellationToken = default) + public Task> ListAsync(CancellationToken cancellationToken = default) { - return new(_tenants); + return Task.FromResult>(_tenants); } - public ValueTask FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + public Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) { var queryable = _tenants.AsQueryable(); - return new ValueTask(filter.Apply(queryable).FirstOrDefault()); + return Task.FromResult(filter.Apply(queryable).FirstOrDefault()); } } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/TaskExecutor.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/TaskExecutor.cs new file mode 100644 index 000000000..6f0062859 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/TaskExecutor.cs @@ -0,0 +1,42 @@ +using System.Reflection; +using Elsa.Common.DistributedHosting; +using Elsa.Common.RecurringTasks; +using Medallion.Threading; +using Microsoft.Extensions.Options; + +namespace Elsa.Common.Multitenancy; + +public class TaskExecutor(IDistributedLockProvider distributedLockProvider, IOptions options) : ITaskExecutor, IBackgroundTaskStarter +{ + public async Task ExecuteTaskAsync(ITask task, CancellationToken cancellationToken) + { + await ExecuteInternalAsync(task, () => task.ExecuteAsync(cancellationToken), cancellationToken); + } + + public async Task StartAsync(IBackgroundTask task, CancellationToken cancellationToken) + { + await ExecuteInternalAsync(task, () => task.StartAsync(cancellationToken), cancellationToken); + } + + public async Task StopAsync(IBackgroundTask task, CancellationToken cancellationToken) + { + await ExecuteInternalAsync(task, () => task.StopAsync(cancellationToken), cancellationToken); + } + + private async Task ExecuteInternalAsync(ITask task, Func action, CancellationToken cancellationToken) + { + var taskType = task.GetType(); + var singleNodeTask = taskType.GetCustomAttribute() != null; + + if (singleNodeTask) + { + var resourceName = taskType.AssemblyQualifiedName!; + await using (await distributedLockProvider.AcquireLockAsync(resourceName, options.Value.LockAcquisitionTimeout, cancellationToken: cancellationToken)) + await action(); + } + else + { + await action(); + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/TenantEventsManager.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/TenantEventsManager.cs new file mode 100644 index 000000000..1c8e85a85 --- /dev/null +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/TenantEventsManager.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Logging; + +namespace Elsa.Common.Multitenancy; + +public class TenantEventsManager(IEnumerable tenantActivatedEvents, IEnumerable tenantDeactivatedEvents, ILogger logger) +{ + public async Task TenantActivatedAsync(TenantActivatedEventArgs args) + { + foreach (var tenantActivatedEvent in tenantActivatedEvents) + { + try + { + await tenantActivatedEvent.TenantActivatedAsync(args); + } + catch (Exception e) + { + logger.LogError(e, "Error occurred while processing tenant activated event."); + } + } + } + + public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args) + { + foreach (var tenantDeactivatedEvent in tenantDeactivatedEvents) + { + try + { + await tenantDeactivatedEvent.TenantDeactivatedAsync(args); + } + catch (Exception e) + { + logger.LogError(e, "Error occurred while processing tenant deactivated event."); + } + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Models/TenantFilter.cs b/src/modules/Elsa.Common/Multitenancy/Models/TenantFilter.cs index 919fba5b2..1fc1c1fa1 100644 --- a/src/modules/Elsa.Common/Multitenancy/Models/TenantFilter.cs +++ b/src/modules/Elsa.Common/Multitenancy/Models/TenantFilter.cs @@ -24,4 +24,6 @@ public class TenantFilter return queryable; } + + public static TenantFilter ById(string id) => new() { Id = id }; } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Multitenancy/Models/TenantScope.cs b/src/modules/Elsa.Common/Multitenancy/Models/TenantScope.cs index 9c937f734..33d6fb260 100644 --- a/src/modules/Elsa.Common/Multitenancy/Models/TenantScope.cs +++ b/src/modules/Elsa.Common/Multitenancy/Models/TenantScope.cs @@ -6,19 +6,16 @@ namespace Elsa.Common.Multitenancy; /// Represents a tenant scope, which sets the current tenant for the duration of the scope. /// After the scope is disposed, the original tenant is restored. /// -public class TenantScope : IAsyncDisposable +public class TenantScope(IServiceScope serviceScope, ITenantAccessor tenantAccessor, Tenant? tenant) : IAsyncDisposable { - public TenantScope(IServiceScope serviceScope, ITenantAccessor tenantAccessor, Tenant? tenant) - { - ServiceScope = serviceScope; - tenantAccessor.Tenant = tenant; - } - - public IServiceScope ServiceScope { get; } + private readonly IDisposable _tenantContext = tenantAccessor.PushContext(tenant); + + public IServiceScope ServiceScope { get; } = serviceScope; public IServiceProvider ServiceProvider => ServiceScope.ServiceProvider; public async ValueTask DisposeAsync() { + _tenantContext.Dispose(); if (ServiceScope is IAsyncDisposable asyncDisposable) await asyncDisposable.DisposeAsync(); else diff --git a/src/modules/Elsa.Common/RecurringTasks/OrderAttribute.cs b/src/modules/Elsa.Common/RecurringTasks/OrderAttribute.cs new file mode 100644 index 000000000..b0efd88b7 --- /dev/null +++ b/src/modules/Elsa.Common/RecurringTasks/OrderAttribute.cs @@ -0,0 +1,10 @@ +namespace Elsa.Common.RecurringTasks; + +/// +/// Configures a task with a priority. +/// +[AttributeUsage(AttributeTargets.Class)] +public class OrderAttribute(float order) : Attribute +{ + public float Order { get; } = order; +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Serialization/ConfigurationJsonConverter.cs b/src/modules/Elsa.Common/Serialization/ConfigurationJsonConverter.cs new file mode 100644 index 000000000..e4a38f799 --- /dev/null +++ b/src/modules/Elsa.Common/Serialization/ConfigurationJsonConverter.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Configuration; + +namespace Elsa.Common.Serialization; + +public class ConfigurationJsonConverter : JsonConverter +{ + public override IConfiguration? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonElement = JsonElement.ParseValue(ref reader); + using var memoryStream = new MemoryStream(); + using var writer = new Utf8JsonWriter(memoryStream); + jsonElement.WriteTo(writer); + writer.Flush(); + memoryStream.Position = 0; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddJsonStream(memoryStream); + return configurationBuilder.Build(); + } + + public override void Write(Utf8JsonWriter writer, IConfiguration value, JsonSerializerOptions options) + { + var dictionary = new Dictionary(); + foreach (var child in value.GetChildren()) + dictionary[child.Key] = GetValue(child); + + JsonSerializer.Serialize(writer, dictionary, options); + } + + private static object? GetValue(IConfigurationSection section) + { + var children = section.GetChildren().ToList(); + + if (!children.Any()) + return section.Value; + + var dict = new Dictionary(); + foreach (var child in children) + dict[child.Key] = GetValue(child); + + return dict; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Serialization/SerializerOptions.cs b/src/modules/Elsa.Common/Serialization/SerializerOptions.cs new file mode 100644 index 000000000..f55c1e6ca --- /dev/null +++ b/src/modules/Elsa.Common/Serialization/SerializerOptions.cs @@ -0,0 +1,11 @@ +using System.Text.Json; + +namespace Elsa.Common.Serialization; + +public static class SerializerOptions +{ + public static JsonSerializerOptions ConfigurationJsonSerializerOptions { get; } = new() + { + Converters = { new ConfigurationJsonConverter() } + }; +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Serialization/Serializers.cs b/src/modules/Elsa.Common/Serialization/Serializers.cs new file mode 100644 index 000000000..f4c9bd745 --- /dev/null +++ b/src/modules/Elsa.Common/Serialization/Serializers.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using Microsoft.Extensions.Configuration; + +namespace Elsa.Common.Serialization; + +public static class Serializers +{ + public static IConfiguration DeserializeConfiguration(JsonElement? json) + { + return DeserializeConfiguration(json?.GetRawText()); + } + + public static IConfiguration DeserializeConfiguration(string? json) + { + if (json == null) + return new ConfigurationBuilder().Build(); + + return JsonSerializer.Deserialize(json, SerializerOptions.ConfigurationJsonSerializerOptions)!; + } + + public static string SerializeConfiguration(IConfiguration configuration) + { + return JsonSerializer.Serialize(configuration, SerializerOptions.ConfigurationJsonSerializerOptions); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Services/MemoryStore.cs b/src/modules/Elsa.Common/Services/MemoryStore.cs index d08fc8603..cbbb7f84f 100644 --- a/src/modules/Elsa.Common/Services/MemoryStore.cs +++ b/src/modules/Elsa.Common/Services/MemoryStore.cs @@ -10,6 +10,11 @@ namespace Elsa.Common.Services; public class MemoryStore { private IDictionary Entities { get; set; } = new ConcurrentDictionary(); + + /// + /// Gets a queryable of all entities. + /// + public IQueryable Queryable => Entities.Values.AsQueryable(); /// /// Adds an entity. diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs b/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs index f788eda18..031f69397 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs @@ -1,4 +1,5 @@ using Elsa.Common.Entities; +using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; @@ -44,11 +45,10 @@ public abstract class PersistenceFeatureBase : FeatureBase .MigrationsAssembly("Elsa.EntityFrameworkCore.Sqlite") .MigrationsHistoryTable(ElsaDbContextBase.MigrationsHistoryTable, ElsaDbContextBase.ElsaSchema)); - /// public override void ConfigureHostedServices() { if (RunMigrations) - Module.ConfigureHostedService>(-100); // Migrations need to run before other hosted services that depend on DB access. + ConfigureMigrations(); } /// @@ -60,6 +60,11 @@ public abstract class PersistenceFeatureBase : FeatureBase Services.AddDbContextFactory(DbContextOptionsBuilder, DbContextFactoryLifetime); } + protected virtual void ConfigureMigrations() + { + Services.AddStartupTask>(); + } + /// /// Adds a store to the service collection. /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs index a2dc5fddd..9bd0f1ac6 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsHostedService.cs @@ -1,21 +1,33 @@ -using Elsa.Common.Multitenancy; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace Elsa.EntityFrameworkCore; /// /// Executes EF Core migrations using the specified type. /// -[UsedImplicitly] -public class RunMigrationsHostedService(IServiceScopeFactory scopeFactory) : MultitenantHostedService(scopeFactory) where TDbContext : DbContext +public class RunMigrationsHostedService : IHostedService where TDbContext : DbContext { - /// - protected override async Task StartAsync(TenantScope tenantScope, CancellationToken cancellationToken) + private readonly IServiceScopeFactory _scopeFactory; + + /// + /// Initializes a new instance of the class. + /// + public RunMigrationsHostedService(IServiceScopeFactory scopeFactory) { - var dbContextFactory = tenantScope.ServiceProvider.GetRequiredService>(); - var tenantDbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); - await tenantDbContext.Database.MigrateAsync(cancellationToken); + _scopeFactory = scopeFactory; } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _scopeFactory.CreateScope(); + var dbContextFactory = scope.ServiceProvider.GetRequiredService>(); + await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + await dbContext.Database.MigrateAsync(cancellationToken); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs new file mode 100644 index 000000000..f55e3b647 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs @@ -0,0 +1,22 @@ +using Elsa.Common; +using Elsa.Common.RecurringTasks; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; + +namespace Elsa.EntityFrameworkCore; + +/// +/// Executes EF Core migrations using the specified type. +/// +[UsedImplicitly] +[SingleNodeTask] +[Order(-100)] +public class RunMigrationsStartupTask(IDbContextFactory dbContextFactory) : IStartupTask where TDbContext : DbContext +{ + /// -{ -} +public class IdentityDbContextFactory : MySqlDesignTimeDbContextFactory; [UsedImplicitly] -public class ManagementDbContextFactory : MySqlDesignTimeDbContextFactory -{ -} +public class ManagementDbContextFactory : MySqlDesignTimeDbContextFactory; [UsedImplicitly] -public class RuntimeDbContextFactory : MySqlDesignTimeDbContextFactory -{ -} +public class RuntimeDbContextFactory : MySqlDesignTimeDbContextFactory; [UsedImplicitly] -public class LabelsDbContextFactory : MySqlDesignTimeDbContextFactory -{ -} +public class LabelsDbContextFactory : MySqlDesignTimeDbContextFactory; [UsedImplicitly] -public class AlterationsDbContextFactories : MySqlDesignTimeDbContextFactory -{ -} +public class AlterationsDbContextFactories : MySqlDesignTimeDbContextFactory; + +[UsedImplicitly] +public class TenantsDbContextFactories : MySqlDesignTimeDbContextFactory; public class MySqlDesignTimeDbContextFactory : DesignTimeDbContextFactoryBase where TDbContext : DbContext { diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20231015122151_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20231015122151_Initial.cs index b9525e6a1..09c731fb3 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20231015122151_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20231015122151_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240329200618_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240329200618_V3_1.cs index cf0ac526b..56b1865c0 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240329200618_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240329200618_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Alterations /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240610184517_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240610184517_V3_2.cs index 8c899c691..ca400108f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240610184517_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20240610184517_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Alterations /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241109102522_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241202095004_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241109102522_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241202095004_V3_3.Designer.cs index 672fe5299..fcf2b0c5f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241109102522_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241202095004_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.MySql.Migrations.Alterations { [DbContext(typeof(AlterationsElsaDbContext))] - [Migration("20241109102522_V3_3")] + [Migration("20241202095004_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241109102522_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241202095004_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241109102522_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Alterations/20241202095004_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20231015122238_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20231015122238_Initial.cs index f0fd329be..69da23b23 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20231015122238_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20231015122238_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240329200746_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240329200746_V3_1.cs index f519f355e..36999aa94 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240329200746_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240329200746_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Identity /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240610184903_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240610184903_V3_2.cs index 2b88f9202..d514c417f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240610184903_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20240610184903_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Identity /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241109102921_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241202095402_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241109102921_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241202095402_V3_3.Designer.cs index 0f4c662c1..bcbe58c02 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241109102921_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241202095402_V3_3.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.MySql.Migrations.Identity { [DbContext(typeof(IdentityElsaDbContext))] - [Migration("20241109102921_V3_3")] + [Migration("20241202095402_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241109102921_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241202095402_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241109102921_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Identity/20241202095402_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20231015122253_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20231015122253_Initial.cs index 3e3ff9f5d..16cb06abf 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20231015122253_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20231015122253_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240329200817_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240329200817_V3_1.cs index f72754325..c6ee183fe 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240329200817_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240329200817_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Labels /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240610184957_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240610184957_V3_2.cs index 4c0682521..290adf17a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240610184957_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20240610184957_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Labels /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241109103004_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241202095515_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241109103004_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241202095515_V3_3.Designer.cs index 66576ea43..660df2293 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241109103004_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241202095515_V3_3.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.MySql.Migrations.Labels { [DbContext(typeof(LabelsElsaDbContext))] - [Migration("20241109103004_V3_3")] + [Migration("20241202095515_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241109103004_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241202095515_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241109103004_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Labels/20241202095515_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20231015122223_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20231015122223_Initial.cs index 0fdb07282..7c52ef258 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20231015122223_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20231015122223_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240329200720_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240329200720_V3_1.cs index 2dcaa7ffe..e4fa114e6 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240329200720_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240329200720_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Management /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240610184802_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240610184802_V3_2.cs index 51ff05acd..523fc6f4a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240610184802_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20240610184802_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Management /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241109102839_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241202095327_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241109102839_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241202095327_V3_3.Designer.cs index 228653e6c..94cb2a082 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241109102839_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241202095327_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.MySql.Migrations.Management { [DbContext(typeof(ManagementElsaDbContext))] - [Migration("20241109102839_V3_3")] + [Migration("20241202095327_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241109102839_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241202095327_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241109102839_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Management/20241202095327_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs index cf3eb698a..41b67f76a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs index 0092d23c1..dcda40b23 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs index 978783615..891b4ba00 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241109102737_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241202095210_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241109102737_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241202095210_V3_3.Designer.cs index f137c8184..8148fb929 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241109102737_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241202095210_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241109102737_V3_3")] + [Migration("20241202095210_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241109102737_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241202095210_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241109102737_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241202095210_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.Designer.cs new file mode 100644 index 000000000..31e86a582 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.Designer.cs @@ -0,0 +1,54 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.MySql.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + [Migration("20241202095623_V3_3")] + partial class V3_3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TenantId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.cs new file mode 100644 index 000000000..a33332d75 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/20241202095623_V3_3.cs @@ -0,0 +1,68 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.MySql.Migrations.Tenants +{ + /// + public partial class V3_3 : Migration + { + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + + /// + public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + { + _schema = schema; + } + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Elsa"); + + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Tenants", + schema: _schema.Schema, + columns: table => new + { + Id = table.Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Name = table.Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Configuration = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + TenantId = table.Column(type: "varchar(255)", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Tenants", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_Name", + schema: _schema.Schema, + table: "Tenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_TenantId", + schema: _schema.Schema, + table: "Tenants", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tenants", + schema: _schema.Schema); + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs new file mode 100644 index 000000000..b02d009ad --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs @@ -0,0 +1,51 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.MySql.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + partial class TenantsElsaDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TenantId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs index 12bfa3a4f..6270c9d91 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs @@ -5,6 +5,7 @@ using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Labels; using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.EntityFrameworkCore.Modules.Tenants; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; @@ -13,29 +14,22 @@ using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.Oracle; [UsedImplicitly] -public class IdentityDbContextFactory : OracleDesignTimeDbContextFactory -{ -} +public class IdentityDbContextFactory : OracleDesignTimeDbContextFactory; [UsedImplicitly] -public class ManagementDbContextFactory : OracleDesignTimeDbContextFactory -{ -} +public class ManagementDbContextFactory : OracleDesignTimeDbContextFactory; [UsedImplicitly] -public class RuntimeDbContextFactory : OracleDesignTimeDbContextFactory -{ -} +public class RuntimeDbContextFactory : OracleDesignTimeDbContextFactory; [UsedImplicitly] -public class LabelsDbContextFactory : OracleDesignTimeDbContextFactory -{ -} +public class LabelsDbContextFactory : OracleDesignTimeDbContextFactory; [UsedImplicitly] -public class AlterationsDbContextFactories : OracleDesignTimeDbContextFactory -{ -} +public class AlterationsDbContextFactories : OracleDesignTimeDbContextFactory; + +[UsedImplicitly] +public class TenantsDbContextFactories : OracleDesignTimeDbContextFactory; public class OracleDesignTimeDbContextFactory : DesignTimeDbContextFactoryBase where TDbContext : DbContext { diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241109102700_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241202095130_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241109102700_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241202095130_V3_3.Designer.cs index bf1e8bf59..1f5ba70a5 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241109102700_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241202095130_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Alterations { [DbContext(typeof(AlterationsElsaDbContext))] - [Migration("20241109102700_V3_3")] + [Migration("20241202095130_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241109102700_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241202095130_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241109102700_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241202095130_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241109102957_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241202095500_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241109102957_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241202095500_V3_3.Designer.cs index 45de3c4db..5032e7618 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241109102957_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241202095500_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Identity { [DbContext(typeof(IdentityElsaDbContext))] - [Migration("20241109102957_V3_3")] + [Migration("20241202095500_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241109102957_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241202095500_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241109102957_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241202095500_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241109103034_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241202095616_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241109103034_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241202095616_V3_3.Designer.cs index 8ca380671..fa998614a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241109103034_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241202095616_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Labels { [DbContext(typeof(LabelsElsaDbContext))] - [Migration("20241109103034_V3_3")] + [Migration("20241202095616_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241109103034_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241202095616_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241109103034_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241202095616_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241109102913_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241202095357_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241109102913_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241202095357_V3_3.Designer.cs index bb97d1bf6..6ebf69eb5 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241109102913_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241202095357_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management { [DbContext(typeof(ManagementElsaDbContext))] - [Migration("20241109102913_V3_3")] + [Migration("20241202095357_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241109102913_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241202095357_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241109102913_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241202095357_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241109102830_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241202095317_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241109102830_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241202095317_V3_3.Designer.cs index 8241699d7..945ec26f6 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241109102830_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241202095317_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241109102830_V3_3")] + [Migration("20241202095317_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241109102830_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241202095317_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241109102830_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241202095317_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.Designer.cs new file mode 100644 index 000000000..4ea554b11 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.Designer.cs @@ -0,0 +1,57 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Oracle.EntityFrameworkCore.Metadata; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + [Migration("20241202095654_V3_3")] + partial class V3_3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("NVARCHAR2(450)"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("NVARCHAR2(2000)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("NVARCHAR2(450)"); + + b.Property("TenantId") + .HasColumnType("NVARCHAR2(450)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.cs new file mode 100644 index 000000000..8ca9f1711 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241202095654_V3_3.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants +{ + /// + public partial class V3_3 : Migration + { + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + + /// + public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + { + _schema = schema; + } + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Elsa"); + + migrationBuilder.CreateTable( + name: "Tenants", + schema: _schema.Schema, + columns: table => new + { + Id = table.Column(type: "NVARCHAR2(450)", nullable: false), + Name = table.Column(type: "NVARCHAR2(450)", nullable: false), + Configuration = table.Column(type: "NVARCHAR2(2000)", nullable: false), + TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tenants", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_Name", + schema: _schema.Schema, + table: "Tenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_TenantId", + schema: _schema.Schema, + table: "Tenants", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tenants", + schema: _schema.Schema); + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs new file mode 100644 index 000000000..464fe8f3f --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Oracle.EntityFrameworkCore.Metadata; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + partial class TenantsElsaDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("NVARCHAR2(450)"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("NVARCHAR2(2000)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("NVARCHAR2(450)"); + + b.Property("TenantId") + .HasColumnType("NVARCHAR2(450)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/DbContextFactories.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/DbContextFactories.cs index 6a629ba88..dfe77cf43 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/DbContextFactories.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/DbContextFactories.cs @@ -5,6 +5,7 @@ using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Labels; using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.EntityFrameworkCore.Modules.Tenants; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; @@ -13,29 +14,22 @@ using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.PostgreSql; [UsedImplicitly] -public class IdentityDbContextFactory : PostgreSqlDesignTimeDbContextFactory -{ -} +public class IdentityDbContextFactory : PostgreSqlDesignTimeDbContextFactory; [UsedImplicitly] -public class ManagementDbContextFactory : PostgreSqlDesignTimeDbContextFactory -{ -} +public class ManagementDbContextFactory : PostgreSqlDesignTimeDbContextFactory; [UsedImplicitly] -public class RuntimeDbContextFactory : PostgreSqlDesignTimeDbContextFactory -{ -} +public class RuntimeDbContextFactory : PostgreSqlDesignTimeDbContextFactory; [UsedImplicitly] -public class LabelsDbContextFactory : PostgreSqlDesignTimeDbContextFactory -{ -} +public class LabelsDbContextFactory : PostgreSqlDesignTimeDbContextFactory; [UsedImplicitly] -public class AlterationsDbContextFactories : PostgreSqlDesignTimeDbContextFactory -{ -} +public class AlterationsDbContextFactories : PostgreSqlDesignTimeDbContextFactory; + +[UsedImplicitly] +public class TenantsDbContextFactories : PostgreSqlDesignTimeDbContextFactory; public class PostgreSqlDesignTimeDbContextFactory : DesignTimeDbContextFactoryBase where TDbContext : DbContext { diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20231015122203_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20231015122203_Initial.cs index 69208cdd5..e321ce433 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20231015122203_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20231015122203_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240329200643_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240329200643_V3_1.cs index fe20a7fa6..ccb181c7b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240329200643_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240329200643_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Alterations /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240610184626_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240610184626_V3_2.cs index c18d33c11..28f51ffff 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240610184626_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20240610184626_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Alterations /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241109102646_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241202095104_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241109102646_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241202095104_V3_3.Designer.cs index 6e3295992..dcead6a8a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241109102646_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241202095104_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Alterations { [DbContext(typeof(AlterationsElsaDbContext))] - [Migration("20241109102646_V3_3")] + [Migration("20241202095104_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241109102646_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241202095104_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241109102646_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Alterations/20241202095104_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20231015122250_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20231015122250_Initial.cs index 937382787..4ae51be42 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20231015122250_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20231015122250_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240329200810_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240329200810_V3_1.cs index cb5dac026..f6f30f7dc 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240329200810_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240329200810_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Identity /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240610184941_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240610184941_V3_2.cs index 021c8cb52..d06fe1bca 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240610184941_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20240610184941_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Identity /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241109102950_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241202095450_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241109102950_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241202095450_V3_3.Designer.cs index dfbd6043b..f02b7bf5e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241109102950_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241202095450_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Identity { [DbContext(typeof(IdentityElsaDbContext))] - [Migration("20241109102950_V3_3")] + [Migration("20241202095450_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241109102950_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241202095450_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241109102950_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Identity/20241202095450_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20231015122304_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20231015122304_Initial.cs index 3cfeff302..f8d3a3f9f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20231015122304_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20231015122304_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240329200841_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240329200841_V3_1.cs index 9822c1ed8..84c472bdd 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240329200841_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240329200841_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Labels /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240610185037_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240610185037_V3_2.cs index 3a9a9f685..e41d8e45d 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240610185037_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20240610185037_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Labels /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241109103027_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241202095605_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241109103027_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241202095605_V3_3.Designer.cs index 08eb659ca..5e37abbff 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241109103027_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241202095605_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Labels { [DbContext(typeof(LabelsElsaDbContext))] - [Migration("20241109103027_V3_3")] + [Migration("20241202095605_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241109103027_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241202095605_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241109103027_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Labels/20241202095605_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20231015122234_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20231015122234_Initial.cs index 79255a952..6b24c6531 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20231015122234_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20231015122234_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240329200740_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240329200740_V3_1.cs index 2d2898c34..dcc4b0e75 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240329200740_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240329200740_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Management /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240610184839_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240610184839_V3_2.cs index 24fcf84a7..5ca565daa 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240610184839_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20240610184839_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Management /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241109102906_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241202095351_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241109102906_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241202095351_V3_3.Designer.cs index a642e2c26..f3f0bc0bb 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241109102906_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241202095351_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Management { [DbContext(typeof(ManagementElsaDbContext))] - [Migration("20241109102906_V3_3")] + [Migration("20241202095351_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241109102906_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241202095351_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241109102906_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Management/20241202095351_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs index 8412c641b..ddf80899d 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs index 821d29f2e..00dfc44e8 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs index d61e4aec4..830c75445 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241109102821_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241202095308_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241109102821_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241202095308_V3_3.Designer.cs index 1ca048a88..20033eb33 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241109102821_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241202095308_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241109102821_V3_3")] + [Migration("20241202095308_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241109102821_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241202095308_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241109102821_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241202095308_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.Designer.cs new file mode 100644 index 000000000..9e1361596 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.Designer.cs @@ -0,0 +1,57 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + [Migration("20241202095647_V3_3")] + partial class V3_3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.cs new file mode 100644 index 000000000..6b0ed9d5e --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/20241202095647_V3_3.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Tenants +{ + /// + public partial class V3_3 : Migration + { + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + + /// + public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + { + _schema = schema; + } + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Elsa"); + + migrationBuilder.CreateTable( + name: "Tenants", + schema: _schema.Schema, + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Configuration = table.Column(type: "text", nullable: false), + TenantId = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tenants", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_Name", + schema: _schema.Schema, + table: "Tenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_TenantId", + schema: _schema.Schema, + table: "Tenants", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tenants", + schema: _schema.Schema); + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs new file mode 100644 index 000000000..668b69844 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + partial class TenantsElsaDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/DbContextFactories.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/DbContextFactories.cs index 07ef6aebc..389dfea6a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/DbContextFactories.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/DbContextFactories.cs @@ -5,6 +5,7 @@ using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Labels; using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.EntityFrameworkCore.Modules.Tenants; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; @@ -13,29 +14,22 @@ using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.SqlServer; [UsedImplicitly] -public class IdentityDbContextFactory : SqlServerDesignTimeDbContextFactory -{ -} +public class IdentityDbContextFactory : SqlServerDesignTimeDbContextFactory; [UsedImplicitly] -public class ManagementDbContextFactory : SqlServerDesignTimeDbContextFactory -{ -} +public class ManagementDbContextFactory : SqlServerDesignTimeDbContextFactory; [UsedImplicitly] -public class RuntimeDbContextFactory : SqlServerDesignTimeDbContextFactory -{ -} +public class RuntimeDbContextFactory : SqlServerDesignTimeDbContextFactory; [UsedImplicitly] -public class LabelsDbContextFactory : SqlServerDesignTimeDbContextFactory -{ -} +public class LabelsDbContextFactory : SqlServerDesignTimeDbContextFactory; [UsedImplicitly] -public class AlterationsDbContextFactories : SqlServerDesignTimeDbContextFactory -{ -} +public class AlterationsDbContextFactories : SqlServerDesignTimeDbContextFactory; + +[UsedImplicitly] +public class TenantsDbContextFactories : SqlServerDesignTimeDbContextFactory; public class SqlServerDesignTimeDbContextFactory : DesignTimeDbContextFactoryBase where TDbContext : DbContext { diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20231015122155_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20231015122155_Initial.cs index 753c472ee..7e0abe564 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20231015122155_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20231015122155_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240329200626_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240329200626_V3_1.cs index c21ccef5c..6da00c13d 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240329200626_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240329200626_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Alterations /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240610184545_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240610184545_V3_2.cs index 6d06f36c3..7aa2f7e99 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240610184545_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20240610184545_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Alterations /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241109102610_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241202095036_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241109102610_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241202095036_V3_3.Designer.cs index e8bf71598..4d7157086 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241109102610_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241202095036_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Alterations { [DbContext(typeof(AlterationsElsaDbContext))] - [Migration("20241109102610_V3_3")] + [Migration("20241202095036_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241109102610_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241202095036_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241109102610_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Alterations/20241202095036_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20231015122242_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20231015122242_Initial.cs index 50ade9cf4..96d07566f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20231015122242_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20231015122242_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240329200753_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240329200753_V3_1.cs index 11a90a6e3..651a17c81 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240329200753_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240329200753_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Identity /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240610184919_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240610184919_V3_2.cs index 303283c60..f174820f5 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240610184919_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20240610184919_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Identity /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241109102931_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241202095420_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241109102931_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241202095420_V3_3.Designer.cs index a6e4eaa39..3a7e1bc51 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241109102931_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241202095420_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Identity { [DbContext(typeof(IdentityElsaDbContext))] - [Migration("20241109102931_V3_3")] + [Migration("20241202095420_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241109102931_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241202095420_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241109102931_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Identity/20241202095420_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20231015122257_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20231015122257_Initial.cs index cb9b030d2..fe9690515 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20231015122257_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20231015122257_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240329200825_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240329200825_V3_1.cs index 923f2a6ae..d7253cb7a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240329200825_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240329200825_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Labels /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240610185009_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240610185009_V3_2.cs index 5e12d4225..ef6e41cbe 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240610185009_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20240610185009_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Labels /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241109103011_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241202095545_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241109103011_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241202095545_V3_3.Designer.cs index 402e5974d..b7fcb321b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241109103011_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241202095545_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Labels { [DbContext(typeof(LabelsElsaDbContext))] - [Migration("20241109103011_V3_3")] + [Migration("20241202095545_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241109103011_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241202095545_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241109103011_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Labels/20241202095545_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20231015122227_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20231015122227_Initial.cs index b4415441c..372bead26 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20231015122227_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20231015122227_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240329200727_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240329200727_V3_1.cs index e2544fa74..4ab327712 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240329200727_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240329200727_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Management /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240610184814_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240610184814_V3_2.cs index 0c134db30..a2bab765b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240610184814_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20240610184814_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Management /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241109102850_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241202095340_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241109102850_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241202095340_V3_3.Designer.cs index 83ae34d9b..f1379d5dd 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241109102850_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241202095340_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Management { [DbContext(typeof(ManagementElsaDbContext))] - [Migration("20241109102850_V3_3")] + [Migration("20241202095340_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241109102850_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241202095340_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241109102850_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Management/20241202095340_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs index 85ae54f44..899bf632b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs @@ -1,5 +1,4 @@ -using Elsa.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs index 342469eec..abaac578e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs index 4f0f4c3ef..67ba36b5f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241109102758_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241202095236_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241109102758_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241202095236_V3_3.Designer.cs index 27cb62198..863317795 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241109102758_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241202095236_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241109102758_V3_3")] + [Migration("20241202095236_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241109102758_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241202095236_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241109102758_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241202095236_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.Designer.cs new file mode 100644 index 000000000..8b313d41e --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.Designer.cs @@ -0,0 +1,57 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + [Migration("20241202095629_V3_3")] + partial class V3_3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.cs new file mode 100644 index 000000000..3b72b2b83 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/20241202095629_V3_3.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Tenants +{ + /// + public partial class V3_3 : Migration + { + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + + /// + public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + { + _schema = schema; + } + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Elsa"); + + migrationBuilder.CreateTable( + name: "Tenants", + schema: _schema.Schema, + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Configuration = table.Column(type: "nvarchar(max)", nullable: false), + TenantId = table.Column(type: "nvarchar(450)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tenants", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_Name", + schema: _schema.Schema, + table: "Tenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_TenantId", + schema: _schema.Schema, + table: "Tenants", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tenants", + schema: _schema.Schema); + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs new file mode 100644 index 000000000..2f58ff947 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + partial class TenantsElsaDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Elsa") + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("TenantId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants", "Elsa"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/DbContextFactories.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/DbContextFactories.cs index 9447034b3..3b73dee21 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/DbContextFactories.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/DbContextFactories.cs @@ -5,6 +5,7 @@ using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Labels; using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.EntityFrameworkCore.Modules.Tenants; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; @@ -13,29 +14,22 @@ using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.Sqlite; [UsedImplicitly] -public class IdentityDbContextFactory : SqliteDesignTimeDbContextFactory -{ -} +public class IdentityDbContextFactory : SqliteDesignTimeDbContextFactory; [UsedImplicitly] -public class ManagementDbContextFactory : SqliteDesignTimeDbContextFactory -{ -} +public class ManagementDbContextFactory : SqliteDesignTimeDbContextFactory; [UsedImplicitly] -public class RuntimeDbContextFactory : SqliteDesignTimeDbContextFactory -{ -} +public class RuntimeDbContextFactory : SqliteDesignTimeDbContextFactory; [UsedImplicitly] -public class LabelsDbContextFactory : SqliteDesignTimeDbContextFactory -{ -} +public class LabelsDbContextFactory : SqliteDesignTimeDbContextFactory; [UsedImplicitly] -public class AlterationsDbContextFactories : SqliteDesignTimeDbContextFactory -{ -} +public class AlterationsDbContextFactories : SqliteDesignTimeDbContextFactory; + +[UsedImplicitly] +public class TenantsDbContextFactories : SqliteDesignTimeDbContextFactory; public class SqliteDesignTimeDbContextFactory : DesignTimeDbContextFactoryBase where TDbContext : DbContext { diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240329200634_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240329200634_V3_1.cs index ac3f405b6..c0aa49c2c 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240329200634_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240329200634_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Alterations /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240610184608_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240610184608_V3_2.cs index b30f310fd..f779303aa 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240610184608_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20240610184608_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Alterations /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241109102628_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241202095051_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241109102628_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241202095051_V3_3.Designer.cs index 14c4d7237..a6208f7a6 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241109102628_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241202095051_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Alterations { [DbContext(typeof(AlterationsElsaDbContext))] - [Migration("20241109102628_V3_3")] + [Migration("20241202095051_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241109102628_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241202095051_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241109102628_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Alterations/20241202095051_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240329200800_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240329200800_V3_1.cs index c6831eeb0..6e2aa0f3a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240329200800_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240329200800_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Identity /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240610184930_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240610184930_V3_2.cs index 1359682c2..3c43a28b7 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240610184930_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20240610184930_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Identity /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241109102941_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241202095439_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241109102941_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241202095439_V3_3.Designer.cs index b901f47f7..845a6939b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241109102941_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241202095439_V3_3.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Identity { [DbContext(typeof(IdentityElsaDbContext))] - [Migration("20241109102941_V3_3")] + [Migration("20241202095439_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241109102941_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241202095439_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241109102941_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Identity/20241202095439_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240329200834_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240329200834_V3_1.cs index 6ead24bbb..1f8f234ef 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240329200834_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240329200834_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Labels /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240610185018_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240610185018_V3_2.cs index cf60cf64e..73bcf04f5 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240610185018_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20240610185018_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Labels /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241109103018_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241202095558_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241109103018_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241202095558_V3_3.Designer.cs index ba9d16639..89184719e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241109103018_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241202095558_V3_3.Designer.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Labels { [DbContext(typeof(LabelsElsaDbContext))] - [Migration("20241109103018_V3_3")] + [Migration("20241202095558_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241109103018_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241202095558_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241109103018_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Labels/20241202095558_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240329200734_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240329200734_V3_1.cs index 8c60af176..deaad852f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240329200734_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240329200734_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Management /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240610184829_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240610184829_V3_2.cs index db37bac56..003ae489f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240610184829_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20240610184829_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Management /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241109102858_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241202095346_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241109102858_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241202095346_V3_3.Designer.cs index 449cecdb8..fe7084b4f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241109102858_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241202095346_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Management { [DbContext(typeof(ManagementElsaDbContext))] - [Migration("20241109102858_V3_3")] + [Migration("20241202095346_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241109102858_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241202095346_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241109102858_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Management/20241202095346_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs index c6e73b527..ddea13893 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_1(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs index d84cfc43f..af8aed20a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + private readonly IElsaDbContextSchema _schema; /// - public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + public V3_2(IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241109102811_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241202095254_V3_3.Designer.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241109102811_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241202095254_V3_3.Designer.cs index 0c4389321..06b817177 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241109102811_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241202095254_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241109102811_V3_3")] + [Migration("20241202095254_V3_3")] partial class V3_3 { /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241109102811_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241202095254_V3_3.cs similarity index 100% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241109102811_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241202095254_V3_3.cs diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.Designer.cs new file mode 100644 index 000000000..4f0d71653 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.Designer.cs @@ -0,0 +1,51 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + [Migration("20241202095639_V3_3")] + partial class V3_3 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.20"); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.cs new file mode 100644 index 000000000..055d068c2 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/20241202095639_V3_3.cs @@ -0,0 +1,53 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Tenants +{ + /// + public partial class V3_3 : Migration + { + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; + + /// + public V3_3(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) + { + _schema = schema; + } + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Tenants", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Configuration = table.Column(type: "TEXT", nullable: false), + TenantId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tenants", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_Name", + table: "Tenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_Tenant_TenantId", + table: "Tenants", + column: "TenantId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tenants"); + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs new file mode 100644 index 000000000..734a0bc10 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs @@ -0,0 +1,48 @@ +// +using Elsa.EntityFrameworkCore.Modules.Tenants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Tenants +{ + [DbContext(typeof(TenantsElsaDbContext))] + partial class TenantsElsaDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.20"); + + modelBuilder.Entity("Elsa.Common.Multitenancy.Tenant", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TenantId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_Tenant_Name"); + + b.HasIndex("TenantId") + .HasDatabaseName("IX_Tenant_TenantId"); + + b.ToTable("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/UserStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/UserStore.cs index bd3e64463..882599c0e 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/UserStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/UserStore.cs @@ -7,34 +7,24 @@ namespace Elsa.EntityFrameworkCore.Modules.Identity; /// /// An EF Core implementation of . /// -public class EFCoreUserStore : IUserStore +public class EFCoreUserStore(EntityStore userStore) : IUserStore { - private readonly EntityStore _userStore; - - /// - /// Initializes a new instance of . - /// - public EFCoreUserStore(EntityStore userStore) - { - _userStore = userStore; - } - /// public async Task SaveAsync(User user, CancellationToken cancellationToken = default) { - await _userStore.SaveAsync(user, cancellationToken); + await userStore.SaveAsync(user, cancellationToken); } /// public async Task DeleteAsync(UserFilter filter, CancellationToken cancellationToken = default) { - await _userStore.DeleteWhereAsync(query => Filter(query, filter), cancellationToken); + await userStore.DeleteWhereAsync(query => Filter(query, filter), cancellationToken); } /// public async Task FindAsync(UserFilter filter, CancellationToken cancellationToken = default) { - return await _userStore.FindAsync(query => Filter(query, filter), cancellationToken); + return await userStore.FindAsync(query => Filter(query, filter), cancellationToken); } private static IQueryable Filter(IQueryable query, UserFilter filter) => filter.Apply(query); diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/ConfigurationJsonConverter.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/ConfigurationJsonConverter.cs new file mode 100644 index 000000000..572585c66 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/ConfigurationJsonConverter.cs @@ -0,0 +1,22 @@ +using Elsa.Common.Serialization; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.Extensions.Configuration; + +namespace Elsa.EntityFrameworkCore.Modules.Tenants; + +public class ConfigurationJsonConverter() : ValueConverter( + config => SerializeConfiguration(config), + json => DeserializeConfiguration(json)) +{ + private static string SerializeConfiguration(IConfiguration configuration) + { + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + return Serializers.SerializeConfiguration(configuration); + } + + private static IConfiguration DeserializeConfiguration(string json) + { + if (json == null) throw new ArgumentNullException(nameof(json)); + return Serializers.DeserializeConfiguration(json); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Configurations.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Configurations.cs new file mode 100644 index 000000000..8b81f348a --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Configurations.cs @@ -0,0 +1,19 @@ +using Elsa.Common.Multitenancy; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Elsa.EntityFrameworkCore.Modules.Tenants; + +/// +/// EF Core configuration for . +/// +public class Configurations : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(x => x.Name).IsRequired(); + builder.Property(x => x.Configuration).HasConversion(); + builder.HasIndex(x => x.Name).HasDatabaseName($"IX_{nameof(Tenant)}_{nameof(Tenant.Name)}"); + builder.HasIndex(x => x.TenantId).HasDatabaseName($"IX_{nameof(Tenant)}_{nameof(Tenant.TenantId)}"); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs new file mode 100644 index 000000000..90d4dad28 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs @@ -0,0 +1,31 @@ +using Elsa.Common.Multitenancy; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using NJsonSchema.Annotations; + +namespace Elsa.EntityFrameworkCore.Modules.Tenants; + +/// +/// DB context for the runtime module. +/// +[UsedImplicitly] +public class TenantsElsaDbContext : ElsaDbContextBase +{ + /// + public TenantsElsaDbContext(DbContextOptions options, IServiceProvider serviceProvider) : base(options, serviceProvider) + { + } + + /// + /// The alteration plans. + /// + public DbSet Tenants { get; set; } = default!; + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + var configuration = new Configurations(); + modelBuilder.ApplyConfiguration(configuration); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Extensions.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Extensions.cs new file mode 100644 index 000000000..abb09f263 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Extensions.cs @@ -0,0 +1,18 @@ +using Elsa.Tenants.Features; + +namespace Elsa.EntityFrameworkCore.Modules.Tenants; + +/// +/// Provides extensions to various management related features. +/// +public static class TenantManagementFeatureExtensions +{ + /// + /// Sets up the EF Core persistence provider. + /// + public static TenantManagementFeature UseEntityFrameworkCore(this TenantManagementFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Feature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Feature.cs new file mode 100644 index 000000000..56cab4378 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/Feature.cs @@ -0,0 +1,35 @@ +using Elsa.Common.Multitenancy; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.Tenants.Features; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.EntityFrameworkCore.Modules.Tenants; + +/// +/// Configures the feature with an Entity Framework Core persistence provider. +/// +[DependsOn(typeof(TenantManagementFeature))] +public class EFCoreTenantManagementFeature(IModule module) : PersistenceFeatureBase(module) +{ + /// + public override void Configure() + { + Module.Configure(feature => + { + feature.WithTenantStore(sp => sp.GetRequiredService()); + }); + } + + /// + public override void Apply() + { + base.Apply(); + AddEntityStore(); + } + + protected override void ConfigureMigrations() + { + Module.ConfigureHostedService>(-100); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/TenantStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/TenantStore.cs new file mode 100644 index 000000000..b3e4eba9e --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/TenantStore.cs @@ -0,0 +1,54 @@ +using Elsa.Common.Multitenancy; +using Elsa.Tenants; +using JetBrains.Annotations; + +namespace Elsa.EntityFrameworkCore.Modules.Tenants; + +[UsedImplicitly] +public class EFCoreTenantStore(EntityStore store) : ITenantStore +{ + public async Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return await store.FindAsync(query => Filter(query, filter), cancellationToken); + } + + public async Task FindAsync(string id, CancellationToken cancellationToken = default) + { + var filter = TenantFilter.ById(id); + return await FindAsync(filter, cancellationToken); + } + + public async Task> FindManyAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return await store.QueryAsync(queryable => Filter(queryable, filter), cancellationToken); + } + + public async Task> ListAsync(CancellationToken cancellationToken = default) + { + return await store.ListAsync(cancellationToken); + } + + public async Task AddAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + await store.AddAsync(tenant, cancellationToken); + } + + public async Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + await store.UpdateAsync(tenant, cancellationToken); + } + + public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) + { + var filter = TenantFilter.ById(id); + var count = await DeleteAsync(filter, cancellationToken); + return count > 0; + } + + public async Task DeleteAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return await store.DeleteWhereAsync(query => Filter(query, filter), cancellationToken); + } + + private static IQueryable Filter(IQueryable query, TenantFilter filter) => filter.Apply(query); +} \ No newline at end of file diff --git a/src/modules/Elsa.Http/Services/DefaultHttpEndpointBasePathProvider.cs b/src/modules/Elsa.Http/Services/DefaultHttpEndpointBasePathProvider.cs index 29390a1e3..de570dbe2 100644 --- a/src/modules/Elsa.Http/Services/DefaultHttpEndpointBasePathProvider.cs +++ b/src/modules/Elsa.Http/Services/DefaultHttpEndpointBasePathProvider.cs @@ -9,7 +9,7 @@ public class DefaultHttpEndpointBasePathProvider(IOptions o { public string GetBasePath() { - var baseUrl = new Uri(options.Value.BaseUrl.ToString().TrimEnd('/')); + var baseUrl = new Uri(options.Value.BaseUrl.ToString().TrimEnd('/') + '/'); var basePath = options.Value.BasePath?.ToString().Trim('/'); var completeBaseUrl = new Uri(baseUrl, basePath); diff --git a/src/modules/Elsa.Kafka/Activities/ProduceMessage.cs b/src/modules/Elsa.Kafka/Activities/ProduceMessage.cs index 381c423b1..01d92bceb 100644 --- a/src/modules/Elsa.Kafka/Activities/ProduceMessage.cs +++ b/src/modules/Elsa.Kafka/Activities/ProduceMessage.cs @@ -68,7 +68,7 @@ public class ProduceMessage : CodeActivity var producerDefinitionEnumerator = context.GetRequiredService(); var producerDefinition = await producerDefinitionEnumerator.GetByIdAsync(producerDefinitionId); var content = Content.Get(context); - var key = Key.Get(context); + var key = Key.GetOrDefault(context); if (key is string keyString && string.IsNullOrWhiteSpace(keyString)) key = null; diff --git a/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs b/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs index 296f0bd14..c070fd188 100644 --- a/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs +++ b/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs @@ -44,7 +44,7 @@ public class MongoDbStore(IMongoCollection collection, ITe var documentsList = documents.ToList(); if (!documentsList.Any()) - return; + return; ApplyTenantId(documentsList); await collection.InsertManyAsync(documentsList, new InsertManyOptions(), cancellationToken); @@ -281,7 +281,16 @@ public class MongoDbStore(IMongoCollection collection, ITe public async Task CountAsync(Func, IQueryable> query, Expression> propertySelector, bool tenantAgnostic = false, CancellationToken cancellationToken = default) { var queryable = GetQueryableCollection(tenantAgnostic); - return await query((IQueryable)queryable.DistinctBy(propertySelector)).LongCountAsync(cancellationToken); + return await query(queryable.DistinctBy(propertySelector)).LongCountAsync(cancellationToken); + } + + /// + /// Lists all documents. + /// + public async Task> ListAsync(bool tenantAgnostic = false, CancellationToken cancellationToken = default) + { + var queryable = GetQueryableCollection(tenantAgnostic); + return await queryable.ToListAsync(cancellationToken); } /// @@ -385,21 +394,21 @@ public class MongoDbStore(IMongoCollection collection, ITe return count; } - + private IQueryable GetQueryableCollection(bool tenantAgnostic = false) { var queryable = collection.AsQueryable(); - - if(tenantAgnostic) + + if (tenantAgnostic) return queryable; - - if(typeof(Entity).IsAssignableFrom(typeof(TDocument))) + + if (typeof(Entity).IsAssignableFrom(typeof(TDocument))) { var tenant = tenantAccessor.Tenant; var tenantId = tenant?.Id; queryable = queryable.Where(x => (x as Entity)!.TenantId == tenantId); } - + return queryable; } diff --git a/src/modules/Elsa.MongoDb/Modules/Tenants/CreateIndices.cs b/src/modules/Elsa.MongoDb/Modules/Tenants/CreateIndices.cs new file mode 100644 index 000000000..1c5df3079 --- /dev/null +++ b/src/modules/Elsa.MongoDb/Modules/Tenants/CreateIndices.cs @@ -0,0 +1,38 @@ +using Elsa.Common.Multitenancy; +using Elsa.MongoDb.Helpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using MongoDB.Driver; + +namespace Elsa.MongoDb.Modules.Tenants; + +internal class CreateIndices(IServiceProvider serviceProvider) : IHostedService +{ + public Task StartAsync(CancellationToken cancellationToken) + { + using var scope = serviceProvider.CreateScope(); + return CreateTenantIndices(scope, cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private static Task CreateTenantIndices(IServiceScope serviceScope, CancellationToken cancellationToken) + { + var mongoCollection = serviceScope.ServiceProvider.GetService>(); + if (mongoCollection == null) return Task.CompletedTask; + + return IndexHelpers.CreateAsync( + mongoCollection, + async (collection, indexBuilder) => + await collection.Indexes.CreateManyAsync( + new List> + { + new(indexBuilder.Ascending(x => x.Name)), + new(indexBuilder.Ascending(x => x.TenantId)) + }, + cancellationToken)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.MongoDb/Modules/Tenants/Extensions.cs b/src/modules/Elsa.MongoDb/Modules/Tenants/Extensions.cs new file mode 100644 index 000000000..4833d7634 --- /dev/null +++ b/src/modules/Elsa.MongoDb/Modules/Tenants/Extensions.cs @@ -0,0 +1,19 @@ +using Elsa.Identity.Features; +using Elsa.Tenants.Features; + +namespace Elsa.MongoDb.Modules.Tenants; + +/// +/// Provides extensions for +/// +public static class Extensions +{ + /// + /// Sets up the MongoDB persistence provider for the User, Application and Role stores. + /// + public static TenantManagementFeature UseMongoDb(this TenantManagementFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.MongoDb/Modules/Tenants/Feature.cs b/src/modules/Elsa.MongoDb/Modules/Tenants/Feature.cs new file mode 100644 index 000000000..6f835e040 --- /dev/null +++ b/src/modules/Elsa.MongoDb/Modules/Tenants/Feature.cs @@ -0,0 +1,37 @@ +using Elsa.Common.Multitenancy; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.MongoDb.Common; +using Elsa.Tenants.Features; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.MongoDb.Modules.Tenants; + +/// +/// Configures the feature with MongoDB persistence providers. +/// +[DependsOn(typeof(TenantManagementFeature))] +[UsedImplicitly] +public class MongoTenantsPersistenceFeature(IModule module) : PersistenceFeatureBase(module) +{ + /// + public override void Configure() + { + Module.Configure(feature => + { + feature.WithTenantStore(sp => sp.GetRequiredService()); + }); + } + + /// + public override void Apply() + { + base.Apply(); + + AddCollection("tenants"); + AddStore(); + + Services.AddHostedService(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.MongoDb/Modules/Tenants/TenantStore.cs b/src/modules/Elsa.MongoDb/Modules/Tenants/TenantStore.cs new file mode 100644 index 000000000..2ec131dd0 --- /dev/null +++ b/src/modules/Elsa.MongoDb/Modules/Tenants/TenantStore.cs @@ -0,0 +1,69 @@ +using Elsa.Common.Multitenancy; +using Elsa.Identity.Contracts; +using Elsa.MongoDb.Common; +using Elsa.Tenants; +using JetBrains.Annotations; + +namespace Elsa.MongoDb.Modules.Tenants; + +/// +/// A MongoDb implementation of . +/// +[UsedImplicitly] +public class MongoTenantStore(MongoDbStore store) : ITenantStore +{ + /// + public Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return store.FindAsync(query => Filter(query, filter), cancellationToken); + } + + /// + public Task FindAsync(string id, CancellationToken cancellationToken = default) + { + var filter = TenantFilter.ById(id); + return FindAsync(filter, cancellationToken); + } + + /// + public Task> FindManyAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return store.FindManyAsync(queryable => Filter(queryable, filter), cancellationToken); + } + + public async Task> ListAsync(CancellationToken cancellationToken = default) + { + return await store.ListAsync(cancellationToken: cancellationToken); + } + + /// + public Task AddAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + return store.AddAsync(tenant, cancellationToken); + } + + /// + public Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + return store.SaveAsync(tenant, cancellationToken); + } + + /// + public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) + { + var filter = TenantFilter.ById(id); + var count = await DeleteAsync(filter, cancellationToken); + return count > 0; + } + + /// + public Task DeleteAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return store.DeleteWhereAsync(query => Filter(query, filter), x => x.Id, cancellationToken); + } + + private static IQueryable Filter(IQueryable query, TenantFilter filter) + { + return filter.Apply(query); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/MySqlQuartzExtensions.cs b/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/MySqlQuartzExtensions.cs index 3d22c6d49..8ade3f33e 100644 --- a/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/MySqlQuartzExtensions.cs +++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/MySqlQuartzExtensions.cs @@ -35,8 +35,7 @@ public static class MySqlQuartzExtensions store.UseClustering(); }); }; - - // Configure the Quartz hosted service to run migrations. + feature.Module.ConfigureHostedService>(-100); return feature; diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/PostgreSqlQuartzExtensions.cs b/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/PostgreSqlQuartzExtensions.cs index 97199393a..a3e3e25dd 100644 --- a/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/PostgreSqlQuartzExtensions.cs +++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.PostgreSql/PostgreSqlQuartzExtensions.cs @@ -41,7 +41,6 @@ public static class PostgreSqlQuartzExtensions }); }; - // Configure the Quartz hosted service to run migrations. feature.Module.ConfigureHostedService>(-100); return feature; diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/SqlServerQuartzExtensions.cs b/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/SqlServerQuartzExtensions.cs index 65f91a6f7..2358dfe8c 100644 --- a/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/SqlServerQuartzExtensions.cs +++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.SqlServer/SqlServerQuartzExtensions.cs @@ -33,7 +33,6 @@ public static class SqlServerQuartzExtensions { options.ConnectionString = connectionString; options.TablePrefix = "[quartz].qrtz_"; - }); if (useClustering) @@ -41,7 +40,6 @@ public static class SqlServerQuartzExtensions }); }; - // Configure the Quartz hosted service to run migrations. feature.Module.ConfigureHostedService>(-100); return feature; diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/SqliteQuartzExtensions.cs b/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/SqliteQuartzExtensions.cs index fd709dd8c..c7796b28d 100644 --- a/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/SqliteQuartzExtensions.cs +++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.Sqlite/SqliteQuartzExtensions.cs @@ -33,7 +33,6 @@ public static class SqliteQuartzExtensions }); }; - // Configure the Quartz hosted service to run migrations. feature.Module.ConfigureHostedService>(-100); return feature; diff --git a/src/modules/Elsa.Quartz/Extensions/JobExecutionExtensions.cs b/src/modules/Elsa.Quartz/Extensions/JobExecutionExtensions.cs index 736c535a5..32a1564cd 100644 --- a/src/modules/Elsa.Quartz/Extensions/JobExecutionExtensions.cs +++ b/src/modules/Elsa.Quartz/Extensions/JobExecutionExtensions.cs @@ -7,6 +7,9 @@ internal static class JobExecutionExtensions { public static async Task GetTenantAsync(this IJobExecutionContext context, ITenantFinder tenantFinder) { + if(!context.MergedJobDataMap.ContainsKey("TenantId")) + return null; + if(!context.MergedJobDataMap.TryGetString("TenantId", out var tenantId)) return null; diff --git a/src/modules/Elsa.Quartz/Features/QuartzSchedulerFeature.cs b/src/modules/Elsa.Quartz/Features/QuartzSchedulerFeature.cs index cc3b2f60e..6600dfca0 100644 --- a/src/modules/Elsa.Quartz/Features/QuartzSchedulerFeature.cs +++ b/src/modules/Elsa.Quartz/Features/QuartzSchedulerFeature.cs @@ -37,8 +37,6 @@ public class QuartzSchedulerFeature(IModule module) : FeatureBase(module) Services.AddSingleton(); Services.AddSingleton(); Services.AddSingleton(); - Services.AddQuartz(quartz => quartz - .AddJob(job => job.WithIdentity(RunWorkflowJob.JobKey).StoreDurably()) - .AddJob(job => job.WithIdentity(ResumeWorkflowJob.JobKey).StoreDurably())); + Services.AddQuartz(); } } \ No newline at end of file diff --git a/src/modules/Elsa.Quartz/Jobs/ResumeWorkflowJob.cs b/src/modules/Elsa.Quartz/Jobs/ResumeWorkflowJob.cs index 72637d9f0..11551a2c6 100644 --- a/src/modules/Elsa.Quartz/Jobs/ResumeWorkflowJob.cs +++ b/src/modules/Elsa.Quartz/Jobs/ResumeWorkflowJob.cs @@ -14,27 +14,25 @@ namespace Elsa.Quartz.Jobs; /// public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime, IJsonSerializer jsonSerializer, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) : IJob { - /// - /// The job key. - /// - public static readonly JobKey JobKey = new(nameof(ResumeWorkflowJob)); - /// public async Task Execute(IJobExecutionContext context) { - tenantAccessor.Tenant = await context.GetTenantAsync(tenantFinder); - var map = context.MergedJobDataMap; - var serializedActivityHandle = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.ActivityHandle)); - var activityHandle = serializedActivityHandle != null ? jsonSerializer.Deserialize(serializedActivityHandle) : null; - var workflowInstanceId = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.WorkflowInstanceId)); - var workflowClient = await workflowRuntime.CreateClientAsync(workflowInstanceId, context.CancellationToken); - var request = new RunWorkflowInstanceRequest + var tenant = await context.GetTenantAsync(tenantFinder); + using (tenantAccessor.PushContext(tenant)) { - BookmarkId = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.BookmarkId)), - ActivityHandle = activityHandle, - Input = map.GetDictionary(nameof(ScheduleExistingWorkflowInstanceRequest.Input)), - Properties = map.GetDictionary(nameof(ScheduleExistingWorkflowInstanceRequest.Properties)), - }; - await workflowClient.RunInstanceAsync(request, cancellationToken: context.CancellationToken); + var map = context.MergedJobDataMap; + var serializedActivityHandle = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.ActivityHandle)); + var activityHandle = serializedActivityHandle != null! ? jsonSerializer.Deserialize(serializedActivityHandle) : null; + var workflowInstanceId = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.WorkflowInstanceId)); + var workflowClient = await workflowRuntime.CreateClientAsync(workflowInstanceId, context.CancellationToken); + var request = new RunWorkflowInstanceRequest + { + BookmarkId = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.BookmarkId)), + ActivityHandle = activityHandle, + Input = map.GetDictionary(nameof(ScheduleExistingWorkflowInstanceRequest.Input)), + Properties = map.GetDictionary(nameof(ScheduleExistingWorkflowInstanceRequest.Properties)), + }; + await workflowClient.RunInstanceAsync(request, cancellationToken: context.CancellationToken); + } } } \ No newline at end of file diff --git a/src/modules/Elsa.Quartz/Jobs/RunWorkflowJob.cs b/src/modules/Elsa.Quartz/Jobs/RunWorkflowJob.cs index 2f35060c6..82cc7b8e3 100644 --- a/src/modules/Elsa.Quartz/Jobs/RunWorkflowJob.cs +++ b/src/modules/Elsa.Quartz/Jobs/RunWorkflowJob.cs @@ -3,6 +3,7 @@ using Elsa.Extensions; using Elsa.Scheduling; using Elsa.Workflows.Models; using Elsa.Workflows.Runtime; +using Elsa.Workflows.Runtime.Exceptions; using Microsoft.Extensions.Logging; using Quartz; @@ -17,36 +18,47 @@ public class RunWorkflowJob( IWorkflowStarter workflowStarter, ILogger logger) : IJob { - /// - /// The job key. - /// - public static readonly JobKey JobKey = new(nameof(RunWorkflowJob)); - /// public async Task Execute(IJobExecutionContext context) { - tenantAccessor.Tenant = await context.GetTenantAsync(tenantFinder); - var map = context.MergedJobDataMap; - var cancellationToken = context.CancellationToken; + var tenant = await context.GetTenantAsync(tenantFinder); + using (tenantAccessor.PushContext(tenant)) + { + var map = context.MergedJobDataMap; + var cancellationToken = context.CancellationToken; - var startRequest = new StartWorkflowRequest - { - WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionVersionId((string)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.WorkflowDefinitionHandle.DefinitionVersionId))), - CorrelationId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.CorrelationId)), - TriggerActivityId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.TriggerActivityId)), - Input = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Input)), - Properties = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Properties)), - ParentId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.ParentId)) - }; - - var startResponse = await workflowStarter.StartWorkflowAsync(startRequest, cancellationToken); - - if (startResponse.CannotStart) - { - logger.LogWarning("Workflow activation strategy disallowed starting workflow {WorkflowDefinitionHandle} with correlation ID {CorrelationId}", startRequest.WorkflowDefinitionHandle, startRequest.CorrelationId); - return; + var startRequest = new StartWorkflowRequest + { + WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionVersionId((string)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.WorkflowDefinitionHandle.DefinitionVersionId))), + CorrelationId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.CorrelationId)), + TriggerActivityId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.TriggerActivityId)), + Input = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Input)), + Properties = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Properties)), + ParentId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.ParentId)) + }; + + try + { + var startResponse = await workflowStarter.StartWorkflowAsync(startRequest, cancellationToken); + + if (startResponse.CannotStart) + { + logger.LogWarning("Workflow activation strategy disallowed starting workflow {WorkflowDefinitionHandle} with correlation ID {CorrelationId}", startRequest.WorkflowDefinitionHandle, startRequest.CorrelationId); + return; + } + + logger.LogInformation("Started workflow {WorkflowInstanceId} with correlation ID {CorrelationId}", startResponse.WorkflowInstanceId, startRequest.CorrelationId); + } + catch (WorkflowGraphNotFoundException e) + { + logger.LogWarning(e, "Could not find workflow graph for workflow definition handle {WorkflowDefinitionHandle}", startRequest.WorkflowDefinitionHandle); + await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken); + } + catch (Exception e) + { + logger.LogError(e, "An error occurred while starting workflow {WorkflowDefinitionHandle} with correlation ID {CorrelationId}", startRequest.WorkflowDefinitionHandle, startRequest.CorrelationId); + await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken); + } } - - logger.LogInformation("Started workflow {WorkflowInstanceId} with correlation ID {CorrelationId}", startResponse.WorkflowInstanceId, startRequest.CorrelationId); } } \ No newline at end of file diff --git a/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs b/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs index 400028799..a110b9043 100644 --- a/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs +++ b/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs @@ -16,93 +16,100 @@ public class QuartzWorkflowScheduler(ISchedulerFactory schedulerFactoryFactory, public async ValueTask ScheduleAtAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); + var job = JobBuilder.Create() + .WithIdentity(GetRunWorkflowJobKey()) + .Build(); var trigger = TriggerBuilder.Create() - .ForJob(RunWorkflowJob.JobKey) .UsingJobData(CreateJobDataMap(request)) - .WithIdentity(taskName) + .WithIdentity(GetTriggerKey(taskName)) .StartAt(at) .Build(); - if (!await scheduler.CheckExists(trigger.Key, cancellationToken)) - await scheduler.ScheduleJob(trigger, cancellationToken); + if (!await scheduler.CheckExists(job.Key, cancellationToken)) + await scheduler.ScheduleJob(job, trigger, cancellationToken); } /// public async ValueTask ScheduleAtAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); + var job = JobBuilder.Create().WithIdentity(GetResumeWorkflowJobKey()).Build(); var trigger = TriggerBuilder.Create() - .ForJob(ResumeWorkflowJob.JobKey) .UsingJobData(CreateJobDataMap(request)) - .WithIdentity(taskName) + .WithIdentity(GetTriggerKey(taskName)) .StartAt(at) .Build(); - if (!await scheduler.CheckExists(trigger.Key, cancellationToken)) - await scheduler.ScheduleJob(trigger, cancellationToken); + if (!await scheduler.CheckExists(job.Key, cancellationToken)) + await scheduler.ScheduleJob(job, trigger, cancellationToken); } /// public async ValueTask ScheduleRecurringAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset startAt, TimeSpan interval, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); + var job = JobBuilder.Create().WithIdentity(GetRunWorkflowJobKey()).Build(); var trigger = TriggerBuilder.Create() - .ForJob(RunWorkflowJob.JobKey) - .WithIdentity(taskName) + .WithIdentity(GetTriggerKey(taskName)) .UsingJobData(CreateJobDataMap(request)) .StartAt(startAt) .WithSimpleSchedule(schedule => schedule.WithInterval(interval).RepeatForever()) .Build(); - if (!await scheduler.CheckExists(trigger.Key, cancellationToken)) - await scheduler.ScheduleJob(trigger, cancellationToken); + if (!await scheduler.CheckExists(job.Key, cancellationToken)) + await scheduler.ScheduleJob(job, trigger, cancellationToken); } /// public async ValueTask ScheduleRecurringAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset startAt, TimeSpan interval, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); + var job = JobBuilder.Create().WithIdentity(GetResumeWorkflowJobKey()).Build(); var trigger = TriggerBuilder.Create() - .ForJob(ResumeWorkflowJob.JobKey) - .WithIdentity(taskName) + .WithIdentity(GetTriggerKey(taskName)) .UsingJobData(CreateJobDataMap(request)) .StartAt(startAt) .WithSimpleSchedule(schedule => schedule.WithInterval(interval).RepeatForever()) .Build(); - if (!await scheduler.CheckExists(trigger.Key, cancellationToken)) - await scheduler.ScheduleJob(trigger, cancellationToken); + if (!await scheduler.CheckExists(job.Key, cancellationToken)) + await scheduler.ScheduleJob(job, trigger, cancellationToken); } /// public async ValueTask ScheduleCronAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); - var trigger = TriggerBuilder.Create().ForJob(RunWorkflowJob.JobKey).UsingJobData(CreateJobDataMap(request)).WithIdentity(taskName).WithCronSchedule(cronExpression).Build(); + var job = JobBuilder.Create().WithIdentity(GetRunWorkflowJobKey()).Build(); + var trigger = TriggerBuilder.Create() + .UsingJobData(CreateJobDataMap(request)) + .WithIdentity(GetTriggerKey(taskName)) + .WithCronSchedule(cronExpression) + .Build(); - if (!await scheduler.CheckExists(trigger.Key, cancellationToken)) - await scheduler.ScheduleJob(trigger, cancellationToken); + if (!await scheduler.CheckExists(job.Key, cancellationToken)) + await scheduler.ScheduleJob(job, trigger, cancellationToken); } /// public async ValueTask ScheduleCronAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); + var job = JobBuilder.Create().WithIdentity(GetResumeWorkflowJobKey()).Build(); var trigger = TriggerBuilder.Create() - .ForJob(ResumeWorkflowJob.JobKey) .UsingJobData(CreateJobDataMap(request)) - .WithIdentity(taskName) + .WithIdentity(GetTriggerKey(taskName)) .WithCronSchedule(cronExpression).Build(); - if (!await scheduler.CheckExists(trigger.Key, cancellationToken)) - await scheduler.ScheduleJob(trigger, cancellationToken); + if (!await scheduler.CheckExists(job.Key, cancellationToken)) + await scheduler.ScheduleJob(job, trigger, cancellationToken); } /// public async ValueTask UnscheduleAsync(string taskName, CancellationToken cancellationToken = default) { var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken); - var triggerKey = new TriggerKey(taskName); + var triggerKey = GetTriggerKey(taskName); await scheduler.UnscheduleJob(triggerKey, cancellationToken); } @@ -131,4 +138,18 @@ public class QuartzWorkflowScheduler(ISchedulerFactory schedulerFactoryFactory, .AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.ActivityHandle), serializedActivityHandle) .AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.BookmarkId), request.BookmarkId); } + + private JobKey GetRunWorkflowJobKey() => new(nameof(RunWorkflowJob), GetGroupName()); + private JobKey GetResumeWorkflowJobKey() => new(nameof(ResumeWorkflowJob), GetGroupName()); + + private string GetGroupName() + { + var tenantId = tenantAccessor.Tenant?.Id; + return string.IsNullOrWhiteSpace(tenantId) ? "Default" : tenantId; + } + + private TriggerKey GetTriggerKey(string taskName) + { + return new TriggerKey(taskName, GetGroupName()); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs b/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs index 92d62a57b..9cbd1a11c 100644 --- a/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs +++ b/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs @@ -1,4 +1,5 @@ using Elsa.Common.Features; +using Elsa.Common.Multitenancy; using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Attributes; @@ -36,6 +37,9 @@ public class SchedulingFeature : FeatureBase public override void Apply() { Services + .AddSingleton() + .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton(sp => sp.GetRequiredService()) .AddSingleton() .AddSingleton() .AddSingleton(CronParser) diff --git a/src/modules/Elsa.Scheduling/Handlers/DeleteSchedules.cs b/src/modules/Elsa.Scheduling/Handlers/DeleteSchedules.cs index 111258f0a..7c6b38b47 100644 --- a/src/modules/Elsa.Scheduling/Handlers/DeleteSchedules.cs +++ b/src/modules/Elsa.Scheduling/Handlers/DeleteSchedules.cs @@ -9,34 +9,19 @@ namespace Elsa.Scheduling.Handlers; /// /// Deletes scheduled jobs based on deleted workflow triggers and bookmarks. /// -public class DeleteSchedules : - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler +public class DeleteSchedules(ITriggerScheduler triggerScheduler, IBookmarkScheduler bookmarkScheduler, ITriggerStore triggerStore, IBookmarkStore bookmarkStore) + : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { - private readonly ITriggerScheduler _triggerScheduler; - private readonly IBookmarkScheduler _bookmarkScheduler; - private readonly ITriggerStore _triggerStore; - private readonly IBookmarkStore _bookmarkStore; - - /// - /// Initializes a new instance of the class. - /// - public DeleteSchedules(ITriggerScheduler triggerScheduler, IBookmarkScheduler bookmarkScheduler, ITriggerStore triggerStore, IBookmarkStore bookmarkStore) - { - _triggerScheduler = triggerScheduler; - _bookmarkScheduler = bookmarkScheduler; - _triggerStore = triggerStore; - _bookmarkStore = bookmarkStore; - } - async Task INotificationHandler.HandleAsync(BookmarksDeleting notification, CancellationToken cancellationToken) { var ids = notification.Bookmarks.Select(x => x.Id).ToList(); - var bookmarks = await _bookmarkStore.FindManyAsync(new BookmarkFilter { BookmarkIds = ids }, cancellationToken); - await _bookmarkScheduler.UnscheduleAsync(bookmarks, cancellationToken); + var bookmarks = await bookmarkStore.FindManyAsync(new BookmarkFilter { BookmarkIds = ids }, cancellationToken); + await bookmarkScheduler.UnscheduleAsync(bookmarks, cancellationToken); } async Task INotificationHandler.HandleAsync(WorkflowDefinitionDeleting notification, CancellationToken cancellationToken) => await RemoveSchedulesAsync(new TriggerFilter { WorkflowDefinitionId = notification.DefinitionId }, cancellationToken); @@ -49,7 +34,7 @@ public class DeleteSchedules : private async Task RemoveSchedulesAsync(TriggerFilter filter, CancellationToken cancellationToken) { - var triggers = await _triggerStore.FindManyAsync(filter, cancellationToken); - await _triggerScheduler.UnscheduleAsync(triggers, cancellationToken); + var triggers = await triggerStore.FindManyAsync(filter, cancellationToken); + await triggerScheduler.UnscheduleAsync(triggers, cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Scheduling/Handlers/UpdateTenantSchedules.cs b/src/modules/Elsa.Scheduling/Handlers/UpdateTenantSchedules.cs new file mode 100644 index 000000000..bdeecad26 --- /dev/null +++ b/src/modules/Elsa.Scheduling/Handlers/UpdateTenantSchedules.cs @@ -0,0 +1,60 @@ +using Elsa.Common.Multitenancy; +using Elsa.Scheduling.Activities; +using Elsa.Workflows.Helpers; +using Elsa.Workflows.Runtime; +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Filters; +using Microsoft.Extensions.DependencyInjection; +using Timer = Elsa.Scheduling.Activities.Timer; + +namespace Elsa.Scheduling.Handlers; + +public class UpdateTenantSchedules : ITenantActivatedEvent, ITenantDeactivatedEvent +{ + private static readonly string[] ActivityTypeNames = + [ + ActivityTypeNameHelper.GenerateTypeName(), + ActivityTypeNameHelper.GenerateTypeName(), + ActivityTypeNameHelper.GenerateTypeName() + ]; + + public async Task TenantActivatedAsync(TenantActivatedEventArgs args) + { + var serviceProvider = args.TenantScope.ServiceProvider; + var cancellationToken = args.CancellationToken; + var triggerScheduler = args.TenantScope.ServiceProvider.GetRequiredService(); + var bookmarkScheduler = args.TenantScope.ServiceProvider.GetRequiredService(); + var triggers = (await GetTriggersAsync(serviceProvider, cancellationToken)).ToList(); + var bookmarks = (await GetBookmarksAsync(serviceProvider, cancellationToken)).ToList(); + + await triggerScheduler.ScheduleAsync(triggers, args.CancellationToken); + await bookmarkScheduler.ScheduleAsync(bookmarks, args.CancellationToken); + } + + public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args) + { + var serviceProvider = args.TenantScope.ServiceProvider; + var cancellationToken = args.CancellationToken; + var triggerScheduler = args.TenantScope.ServiceProvider.GetRequiredService(); + var bookmarkScheduler = args.TenantScope.ServiceProvider.GetRequiredService(); + var triggers = (await GetTriggersAsync(serviceProvider, cancellationToken)).ToList(); + var bookmarks = (await GetBookmarksAsync(serviceProvider, cancellationToken)).ToList(); + + await triggerScheduler.UnscheduleAsync(triggers, args.CancellationToken); + await bookmarkScheduler.UnscheduleAsync(bookmarks, args.CancellationToken); + } + + private async Task> GetTriggersAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var triggerStore = serviceProvider.GetRequiredService(); + var filter = TriggerFilter.ByNames(ActivityTypeNames); + return await triggerStore.FindManyAsync(filter, cancellationToken); + } + + private async Task> GetBookmarksAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) + { + var bookmarkStore = serviceProvider.GetRequiredService(); + var filter = BookmarkFilter.ByActivityTypeNames(ActivityTypeNames); + return await bookmarkStore.FindManyAsync(filter, cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants.AspNetCore/Services/TenantPrefixHttpEndpointBasePathProvider.cs b/src/modules/Elsa.Tenants.AspNetCore/Services/TenantPrefixHttpEndpointBasePathProvider.cs index 7ccf3f809..813435790 100644 --- a/src/modules/Elsa.Tenants.AspNetCore/Services/TenantPrefixHttpEndpointBasePathProvider.cs +++ b/src/modules/Elsa.Tenants.AspNetCore/Services/TenantPrefixHttpEndpointBasePathProvider.cs @@ -22,7 +22,7 @@ public class TenantPrefixHttpEndpointBasePathProvider( return (baseUrl + basePath).TrimEnd('/') + '/'; var tenant = tenantAccessor.Tenant; - var tenantPrefix = tenant?.GetRoutePrefix(); + var tenantPrefix = tenant?.GetRoutePrefix()?.Trim('/'); if(string.IsNullOrWhiteSpace(tenantPrefix)) return (baseUrl + basePath).TrimEnd('/') + '/'; diff --git a/src/modules/Elsa.Tenants/Contracts/ITenantStore.cs b/src/modules/Elsa.Tenants/Contracts/ITenantStore.cs new file mode 100644 index 000000000..30e8b91e5 --- /dev/null +++ b/src/modules/Elsa.Tenants/Contracts/ITenantStore.cs @@ -0,0 +1,15 @@ +using Elsa.Common.Multitenancy; + +namespace Elsa.Tenants; + +public interface ITenantStore +{ + Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default); + Task FindAsync(string id, CancellationToken cancellationToken = default); + Task> FindManyAsync(TenantFilter filter, CancellationToken cancellationToken = default); + Task> ListAsync(CancellationToken cancellationToken = default); + Task AddAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default); + Task DeleteAsync(string id, CancellationToken cancellationToken = default); + Task DeleteAsync(TenantFilter filter, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Elsa.Tenants.csproj b/src/modules/Elsa.Tenants/Elsa.Tenants.csproj index 275d67b58..08b3248df 100644 --- a/src/modules/Elsa.Tenants/Elsa.Tenants.csproj +++ b/src/modules/Elsa.Tenants/Elsa.Tenants.csproj @@ -8,6 +8,7 @@ + diff --git a/src/modules/Elsa.Tenants/Elsa.Tenants.csproj.DotSettings b/src/modules/Elsa.Tenants/Elsa.Tenants.csproj.DotSettings index 4c6e2920c..d882ac78d 100644 --- a/src/modules/Elsa.Tenants/Elsa.Tenants.csproj.DotSettings +++ b/src/modules/Elsa.Tenants/Elsa.Tenants.csproj.DotSettings @@ -1,3 +1,4 @@  True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Endpoints/Tenants/Add/Endpoint.cs b/src/modules/Elsa.Tenants/Endpoints/Tenants/Add/Endpoint.cs new file mode 100644 index 000000000..8a029bdcb --- /dev/null +++ b/src/modules/Elsa.Tenants/Endpoints/Tenants/Add/Endpoint.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using Elsa.Abstractions; +using Elsa.Common.Multitenancy; +using Elsa.Common.Serialization; +using Elsa.Workflows; + +namespace Elsa.Tenants.Endpoints.Tenants.Add; + +public class Endpoint(ITenantService tenantService, IIdentityGenerator identityGenerator, ITenantStore tenantStore) : ElsaEndpoint +{ + public override void Configure() + { + Post("/tenants"); + ConfigurePermissions("write:tenants"); + } + + public override async Task HandleAsync(NewTenant req, CancellationToken ct) + { + var tenant = new Tenant + { + Id = req.IsDefault ? string.Empty : req.Id ?? identityGenerator.GenerateId(), + TenantId = req.TenantId, + Name = req.Name.Trim(), + Configuration = Serializers.DeserializeConfiguration(req.Configuration) + }; + await tenantStore.AddAsync(tenant, ct); + await tenantService.RefreshAsync(ct); + await SendOkAsync(tenant, ct); + } +} + +public class NewTenant +{ + public bool IsDefault { get; set; } + public string? Id { get; set; } + public string? TenantId { get; set; } + public string Name { get; set; } = default!; + public JsonElement? Configuration { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Endpoints/Tenants/Delete/Endpoint.cs b/src/modules/Elsa.Tenants/Endpoints/Tenants/Delete/Endpoint.cs new file mode 100644 index 000000000..96ea75a32 --- /dev/null +++ b/src/modules/Elsa.Tenants/Endpoints/Tenants/Delete/Endpoint.cs @@ -0,0 +1,28 @@ +using Elsa.Abstractions; +using Elsa.Common.Multitenancy; + +namespace Elsa.Tenants.Endpoints.Tenants.Delete; + +public class Endpoint(ITenantService tenantService, ITenantStore store) : ElsaEndpointWithoutRequest +{ + public override void Configure() + { + Delete("/tenants/{id}"); + ConfigurePermissions("delete:tenants"); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var id = Route("id")!; + var found = await store.DeleteAsync(id, ct); + + if (!found) + { + await SendNotFoundAsync(ct); + return; + } + + await tenantService.RefreshAsync(ct); + await SendOkAsync(ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Endpoints/Tenants/Get/Endpoint.cs b/src/modules/Elsa.Tenants/Endpoints/Tenants/Get/Endpoint.cs new file mode 100644 index 000000000..ba8b0dc16 --- /dev/null +++ b/src/modules/Elsa.Tenants/Endpoints/Tenants/Get/Endpoint.cs @@ -0,0 +1,28 @@ +using Elsa.Abstractions; +using Elsa.Common.Multitenancy; +using Elsa.Models; + +namespace Elsa.Tenants.Endpoints.Tenants.Get; + +public class Endpoint(ITenantService tenantService) : ElsaEndpointWithoutRequest +{ + public override void Configure() + { + Get("/tenants/{id}"); + ConfigurePermissions("read:tenants"); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var id = Route("id")!; + var tenant = await tenantService.FindAsync(id, ct); + + if (tenant == null) + { + await SendNotFoundAsync(ct); + return; + } + + await SendOkAsync(tenant, ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Endpoints/Tenants/List/Endpoint.cs b/src/modules/Elsa.Tenants/Endpoints/Tenants/List/Endpoint.cs new file mode 100644 index 000000000..489a581c6 --- /dev/null +++ b/src/modules/Elsa.Tenants/Endpoints/Tenants/List/Endpoint.cs @@ -0,0 +1,25 @@ +using System.Net.Mime; +using System.Text.Json; +using Elsa.Abstractions; +using Elsa.Common.Multitenancy; +using Elsa.Common.Serialization; +using Elsa.Models; + +namespace Elsa.Tenants.Endpoints.Tenants.List; + +public class Endpoint(ITenantService tenantService) : ElsaEndpointWithoutRequest> +{ + public override void Configure() + { + Get("/tenants"); + ConfigurePermissions("read:tenants"); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var tenants = await tenantService.ListAsync(ct); + var response = new ListResponse(tenants.ToList()); + var json = JsonSerializer.Serialize(response, SerializerOptions.ConfigurationJsonSerializerOptions); + await SendStringAsync(json, contentType: MediaTypeNames.Application.Json, cancellation: ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Endpoints/Tenants/Refresh/Endpoint.cs b/src/modules/Elsa.Tenants/Endpoints/Tenants/Refresh/Endpoint.cs new file mode 100644 index 000000000..aa7a3494e --- /dev/null +++ b/src/modules/Elsa.Tenants/Endpoints/Tenants/Refresh/Endpoint.cs @@ -0,0 +1,19 @@ +using Elsa.Abstractions; +using Elsa.Common.Multitenancy; + +namespace Elsa.Tenants.Endpoints.Tenants.Refresh; + +public class Endpoint(ITenantService tenantService) : ElsaEndpointWithoutRequest +{ + public override void Configure() + { + Post("/tenants/refresh"); + ConfigurePermissions("execute:tenants:refresh"); + } + + public override async Task HandleAsync(CancellationToken ct) + { + await tenantService.RefreshAsync(ct); + await SendOkAsync(ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Endpoints/Tenants/Update/Endpoint.cs b/src/modules/Elsa.Tenants/Endpoints/Tenants/Update/Endpoint.cs new file mode 100644 index 000000000..255eeba88 --- /dev/null +++ b/src/modules/Elsa.Tenants/Endpoints/Tenants/Update/Endpoint.cs @@ -0,0 +1,43 @@ +using System.Text.Json; +using Elsa.Abstractions; +using Elsa.Common.Multitenancy; +using Elsa.Common.Serialization; + +namespace Elsa.Tenants.Endpoints.Tenants.Update; + +public class Endpoint(ITenantService tenantService, ITenantStore tenantStore) : ElsaEndpoint +{ + public override void Configure() + { + Post("/tenants/{id}"); + ConfigurePermissions("write:tenants"); + } + + public override async Task HandleAsync(UpdatedTenant req, CancellationToken ct) + { + var id = Route("id")!; + if (id == "-") id = string.Empty; + var tenant = await tenantStore.FindAsync(id, ct); + + if (tenant == null) + { + await SendNotFoundAsync(ct); + return; + } + + tenant.TenantId = req.TenantId; + tenant.Name = req.Name.Trim(); + tenant.Configuration = Serializers.DeserializeConfiguration(req.Configuration); + await tenantStore.UpdateAsync(tenant, ct); + await tenantService.RefreshAsync(ct); + await SendOkAsync(tenant, ct); + } +} + +public class UpdatedTenant +{ + public string Id { get; set; } = default!; + public string? TenantId { get; set; } + public string Name { get; set; } = default!; + public JsonElement? Configuration { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Tenants/Extensions/ModuleExtensions.cs index 052a29495..b8c03eaa0 100644 --- a/src/modules/Elsa.Tenants/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.Tenants/Extensions/ModuleExtensions.cs @@ -17,4 +17,22 @@ public static class ModuleExtensions module.Configure(configure); return module; } + + /// + /// Installs and configures the feature. + /// + public static TenantsFeature UseTenantManagementEndpoints(this TenantsFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } + + /// + /// Installs and configures the feature. + /// + public static TenantsFeature UseTenantManagement(this TenantsFeature feature, Action? configure = default) + { + feature.Module.Configure(configure); + return feature; + } } \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Features/TenantManagementEndpointsFeature.cs b/src/modules/Elsa.Tenants/Features/TenantManagementEndpointsFeature.cs new file mode 100644 index 000000000..af61a1b4e --- /dev/null +++ b/src/modules/Elsa.Tenants/Features/TenantManagementEndpointsFeature.cs @@ -0,0 +1,19 @@ +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Attributes; +using Elsa.Features.Services; + +namespace Elsa.Tenants.Features; + +/// +/// Enables tenant management endpoints. +/// +[DependsOn(typeof(TenantManagementFeature))] +public class TenantManagementEndpointsFeature(IModule serviceConfiguration) : FeatureBase(serviceConfiguration) +{ + /// + public override void Configure() + { + Module.AddFastEndpointsAssembly(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Features/TenantManagementFeature.cs b/src/modules/Elsa.Tenants/Features/TenantManagementFeature.cs new file mode 100644 index 000000000..f957ba567 --- /dev/null +++ b/src/modules/Elsa.Tenants/Features/TenantManagementFeature.cs @@ -0,0 +1,29 @@ +using Elsa.Common.Multitenancy; +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Tenants.Features; + +/// +/// Enables tenant management endpoints. +/// +public class TenantManagementFeature(IModule serviceConfiguration) : FeatureBase(serviceConfiguration) +{ + private Func _tenantStoreFactory = sp => sp.GetRequiredService(); + + public TenantManagementFeature WithTenantStore(Func factory) + { + _tenantStoreFactory = factory; + return this; + } + + /// + public override void Apply() + { + Services + .AddMemoryStore() + .AddScoped(_tenantStoreFactory); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Features/TenantsFeature.cs b/src/modules/Elsa.Tenants/Features/TenantsFeature.cs index 95f6f4d41..a1a850eb4 100644 --- a/src/modules/Elsa.Tenants/Features/TenantsFeature.cs +++ b/src/modules/Elsa.Tenants/Features/TenantsFeature.cs @@ -4,7 +4,6 @@ using Elsa.Features.Abstractions; using Elsa.Features.Services; using Elsa.Tenants.Options; using Elsa.Tenants.Providers; -using Elsa.Tenants.Services; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Tenants.Features; @@ -17,23 +16,37 @@ public class TenantsFeature(IModule serviceConfiguration) : FeatureBase(serviceC /// /// Configures the Tenants options. /// - private Action TenantsOptions { get; set; } = _ => { }; + private Action MultitenancyOptions { get; set; } = _ => { }; - public TenantsFeature ConfigureOptions(Action configure) + private Action TenantsOptions { get; set; } = _ => { }; + + public TenantsFeature ConfigureMultitenancy(Action configure) { Services.Configure(configure); return this; } - public void UseConfigurationBasedTenantsProvider() + public TenantsFeature ConfigureTenants(Action configure) { + Services.Configure(configure); + return this; + } + + public void UseConfigurationBasedTenantsProvider(Action configure) + { + ConfigureTenants(configure); Module.Configure(feature => feature.UseTenantsProvider()); } + + public void UseStoreBasedTenantsProvider() + { + Module.Configure(feature => feature.UseTenantsProvider()); + } /// public override void Apply() { - Services.Configure(TenantsOptions); + Services.Configure(MultitenancyOptions); Services .AddScoped() diff --git a/src/modules/Elsa.Tenants/Options/MultitenancyOptions.cs b/src/modules/Elsa.Tenants/Options/MultitenancyOptions.cs index f701bb425..2ad139414 100644 --- a/src/modules/Elsa.Tenants/Options/MultitenancyOptions.cs +++ b/src/modules/Elsa.Tenants/Options/MultitenancyOptions.cs @@ -1,6 +1,4 @@ using Elsa.Common.Multitenancy; -using Elsa.Tenants.Providers; -using Elsa.Tenants.Services; namespace Elsa.Tenants.Options; @@ -9,11 +7,6 @@ namespace Elsa.Tenants.Options; /// public class MultitenancyOptions { - /// - /// Gets or sets the tenants through configuration. Will be used by the - /// - public ICollection Tenants { get; set; } = new List(); - /// /// Gets or sets the tenant resolution pipeline builder. /// diff --git a/src/modules/Elsa.Tenants/Options/TenantsOptions.cs b/src/modules/Elsa.Tenants/Options/TenantsOptions.cs new file mode 100644 index 000000000..131c67264 --- /dev/null +++ b/src/modules/Elsa.Tenants/Options/TenantsOptions.cs @@ -0,0 +1,15 @@ +using Elsa.Common.Multitenancy; +using Elsa.Tenants.Providers; + +namespace Elsa.Tenants.Options; + +/// +/// Options for configuring the Tenants. +/// +public class TenantsOptions +{ + /// + /// Gets or sets the tenants through configuration. Will be used by the + /// + public ICollection Tenants { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Providers/ConfigurationTenantsProvider.cs b/src/modules/Elsa.Tenants/Providers/ConfigurationTenantsProvider.cs index 9401bd894..80d272edb 100644 --- a/src/modules/Elsa.Tenants/Providers/ConfigurationTenantsProvider.cs +++ b/src/modules/Elsa.Tenants/Providers/ConfigurationTenantsProvider.cs @@ -1,7 +1,8 @@ -using System.Diagnostics.CodeAnalysis; -using Elsa.Common.Multitenancy; +using Elsa.Common.Multitenancy; using Elsa.Tenants.Options; +using JetBrains.Annotations; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Elsa.Tenants.Providers; @@ -9,36 +10,61 @@ namespace Elsa.Tenants.Providers; /// /// Provides the implementation to retrieve tenant information from a configuration. /// +[UsedImplicitly] public class ConfigurationTenantsProvider : ITenantsProvider { - private readonly ICollection _tenants; + private readonly IConfiguration _configuration; + private readonly ITenantService _tenantService; + private readonly ILogger _logger; + private ICollection _tenants = new List(); /// /// Initializes a new instance of the class. /// - public ConfigurationTenantsProvider(IOptions options, IConfiguration configuration) + public ConfigurationTenantsProvider(IOptionsMonitor options, IConfiguration configuration, ITenantService tenantService, ILogger logger) { - var tenants = options.Value.Tenants.ToList(); + _configuration = configuration; + _tenantService = tenantService; + _logger = logger; + UpdateTenants(options.CurrentValue); + options.OnChange(OnOptionsChanged); + } + + private async void OnOptionsChanged(TenantsOptions options, string? name) + { + try + { + UpdateTenants(options); + await _tenantService.RefreshAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "An error occurred while updating tenants."); + } + } + + private void UpdateTenants(TenantsOptions options) + { + var tenants = options.Tenants.ToList(); // Rebind each Tenant's Configuration property manually using array indices for (int i = 0; i < tenants.Count; i++) - tenants[i].Configuration = configuration.GetSection($"Multitenancy:Tenants:{i}:Configuration"); + tenants[i].Configuration = _configuration.GetSection($"Multitenancy:Tenants:{i}:Configuration"); _tenants = tenants; } /// - public ValueTask> ListAsync(CancellationToken cancellationToken = default) + public Task> ListAsync(CancellationToken cancellationToken = default) { - return new(_tenants); + return Task.FromResult>(_tenants); } /// - [RequiresUnreferencedCode("The caller of this method may require dynamic access to the tenant properties.")] - public ValueTask FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + public Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) { var tenantsQueryable = _tenants.AsQueryable(); var tenant = filter.Apply(tenantsQueryable).FirstOrDefault(); - return new(tenant); + return Task.FromResult(tenant); } } diff --git a/src/modules/Elsa.Tenants/Providers/StoreTenantsProvider.cs b/src/modules/Elsa.Tenants/Providers/StoreTenantsProvider.cs new file mode 100644 index 000000000..5c8371059 --- /dev/null +++ b/src/modules/Elsa.Tenants/Providers/StoreTenantsProvider.cs @@ -0,0 +1,18 @@ +using Elsa.Common.Multitenancy; +using JetBrains.Annotations; + +namespace Elsa.Tenants.Providers; + +[UsedImplicitly] +public class StoreTenantsProvider(ITenantStore store) : ITenantsProvider +{ + public async Task> ListAsync(CancellationToken cancellationToken = default) + { + return await store.ListAsync(cancellationToken); + } + + public Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + return store.FindAsync(filter, cancellationToken); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Services/DefaultTenantResolverPipelineInvoker.cs b/src/modules/Elsa.Tenants/Services/DefaultTenantResolverPipelineInvoker.cs index 6df875af2..c0b832f32 100644 --- a/src/modules/Elsa.Tenants/Services/DefaultTenantResolverPipelineInvoker.cs +++ b/src/modules/Elsa.Tenants/Services/DefaultTenantResolverPipelineInvoker.cs @@ -3,7 +3,7 @@ using Elsa.Tenants.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace Elsa.Tenants.Services; +namespace Elsa.Tenants; /// /// Resolves the tenant using a pipeline of resolvers. diff --git a/src/modules/Elsa.Tenants/Services/MemoryTenantStore.cs b/src/modules/Elsa.Tenants/Services/MemoryTenantStore.cs new file mode 100644 index 000000000..d071674a5 --- /dev/null +++ b/src/modules/Elsa.Tenants/Services/MemoryTenantStore.cs @@ -0,0 +1,61 @@ +using Elsa.Common.Multitenancy; +using Elsa.Common.Services; +using JetBrains.Annotations; + +namespace Elsa.Tenants; + +[UsedImplicitly] +public class MemoryTenantStore(MemoryStore store) : ITenantStore +{ + public Task FindAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + var result = store.Query(query => Filter(query, filter)).FirstOrDefault(); + return Task.FromResult(result); + } + + public Task FindAsync(string id, CancellationToken cancellationToken = default) + { + var filter = TenantFilter.ById(id); + return FindAsync(filter, cancellationToken); + } + + public Task> FindManyAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + var result = store.Query(query => Filter(query, filter)).ToList().AsEnumerable(); + return Task.FromResult(result); + } + + public Task> ListAsync(CancellationToken cancellationToken = default) + { + var result = store.List(); + return Task.FromResult(result); + } + + public Task AddAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + store.Add(tenant, GetId); + return Task.CompletedTask; + } + + public Task UpdateAsync(Tenant tenant, CancellationToken cancellationToken = default) + { + store.Update(tenant, GetId); + return Task.CompletedTask; + } + + public Task DeleteAsync(string id, CancellationToken cancellationToken = default) + { + var found = store.Delete(id); + return Task.FromResult(found); + } + + public Task DeleteAsync(TenantFilter filter, CancellationToken cancellationToken = default) + { + var deletedCount = store.DeleteMany(filter.Apply(store.Queryable), GetId); + return Task.FromResult(deletedCount); + } + + private IQueryable Filter(IQueryable queryable, TenantFilter filter) => filter.Apply(queryable); + + private string GetId(Tenant tenant) => tenant.Id; +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants/Services/TenantResolutionPipelineBuilder.cs b/src/modules/Elsa.Tenants/Services/TenantResolutionPipelineBuilder.cs index 82a4bd135..acb456a00 100644 --- a/src/modules/Elsa.Tenants/Services/TenantResolutionPipelineBuilder.cs +++ b/src/modules/Elsa.Tenants/Services/TenantResolutionPipelineBuilder.cs @@ -1,7 +1,7 @@ using Elsa.Common.Multitenancy; using Microsoft.Extensions.DependencyInjection; -namespace Elsa.Tenants.Services; +namespace Elsa.Tenants; /// public class TenantResolverPipelineBuilder : ITenantResolverPipelineBuilder diff --git a/src/modules/Elsa.Workflows.Management/Stores/MemoryWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Management/Stores/MemoryWorkflowDefinitionStore.cs index 557d1e136..d30153a23 100644 --- a/src/modules/Elsa.Workflows.Management/Stores/MemoryWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Management/Stores/MemoryWorkflowDefinitionStore.cs @@ -10,138 +10,128 @@ namespace Elsa.Workflows.Management.Stores; /// /// A memory implementation of . /// -public class MemoryWorkflowDefinitionStore : IWorkflowDefinitionStore +public class MemoryWorkflowDefinitionStore(MemoryStore store) : IWorkflowDefinitionStore { - private readonly MemoryStore _store; - - /// - /// Constructor. - /// - public MemoryWorkflowDefinitionStore(MemoryStore store) - { - _store = store; - } - /// public Task FindAsync(WorkflowDefinitionFilter filter, CancellationToken cancellationToken = default) { - var result = _store.Query(query => Filter(query, filter)).FirstOrDefault(); + var result = store.Query(query => Filter(query, filter)).FirstOrDefault(); return Task.FromResult(result); } /// public Task FindAsync(WorkflowDefinitionFilter filter, WorkflowDefinitionOrder order, CancellationToken cancellationToken = default) { - var result = _store.Query(query => Filter(query, filter).OrderBy(order)).FirstOrDefault(); + var result = store.Query(query => Filter(query, filter).OrderBy(order)).FirstOrDefault(); return Task.FromResult(result); } /// public Task> FindManyAsync(WorkflowDefinitionFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default) { - var count = _store.Query(query => Filter(query, filter)).LongCount(); - var result = _store.Query(query => Filter(query, filter).Paginate(pageArgs)).ToList(); + var count = store.Query(query => Filter(query, filter)).LongCount(); + var result = store.Query(query => Filter(query, filter).Paginate(pageArgs)).ToList(); return Task.FromResult(Page.Of(result, count)); } /// public Task> FindManyAsync(WorkflowDefinitionFilter filter, WorkflowDefinitionOrder order, PageArgs pageArgs, CancellationToken cancellationToken = default) { - var count = _store.Query(query => Filter(query, filter).OrderBy(order)).LongCount(); - var result = _store.Query(query => Filter(query, filter).Paginate(pageArgs)).ToList(); + var count = store.Query(query => Filter(query, filter).OrderBy(order)).LongCount(); + var result = store.Query(query => Filter(query, filter).Paginate(pageArgs)).ToList(); return Task.FromResult(Page.Of(result, count)); } /// public Task> FindManyAsync(WorkflowDefinitionFilter filter, CancellationToken cancellationToken = default) { - var result = _store.Query(query => Filter(query, filter)).ToList().AsEnumerable(); + var result = store.Query(query => Filter(query, filter)).ToList().AsEnumerable(); return Task.FromResult(result); } /// public Task> FindManyAsync(WorkflowDefinitionFilter filter, WorkflowDefinitionOrder order, CancellationToken cancellationToken = default) { - var result = _store.Query(query => Filter(query, filter).OrderBy(order)).ToList().AsEnumerable(); + var result = store.Query(query => Filter(query, filter).OrderBy(order)).ToList().AsEnumerable(); return Task.FromResult(result); } /// public Task> FindSummariesAsync(WorkflowDefinitionFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default) { - var count = _store.Query(query => Filter(query, filter)).LongCount(); - var result = _store.Query(query => Filter(query, filter).Paginate(pageArgs)).Select(WorkflowDefinitionSummary.FromDefinition).ToList(); + var count = store.Query(query => Filter(query, filter)).LongCount(); + var result = store.Query(query => Filter(query, filter).Paginate(pageArgs)).Select(WorkflowDefinitionSummary.FromDefinition).ToList(); return Task.FromResult(Page.Of(result, count)); } /// public Task> FindSummariesAsync(WorkflowDefinitionFilter filter, WorkflowDefinitionOrder order, PageArgs pageArgs, CancellationToken cancellationToken = default) { - var count = _store.Query(query => Filter(query, filter).OrderBy(order)).LongCount(); - var result = _store.Query(query => Filter(query, filter).Paginate(pageArgs)).Select(WorkflowDefinitionSummary.FromDefinition).ToList(); + var count = store.Query(query => Filter(query, filter).OrderBy(order)).LongCount(); + var result = store.Query(query => Filter(query, filter).Paginate(pageArgs)).Select(WorkflowDefinitionSummary.FromDefinition).ToList(); return Task.FromResult(Page.Of(result, count)); } /// public Task> FindSummariesAsync(WorkflowDefinitionFilter filter, CancellationToken cancellationToken = default) { - var result = _store.Query(query => Filter(query, filter)).Select(WorkflowDefinitionSummary.FromDefinition).ToList().AsEnumerable(); + var result = store.Query(query => Filter(query, filter)).Select(WorkflowDefinitionSummary.FromDefinition).ToList().AsEnumerable(); return Task.FromResult(result); } /// public Task> FindSummariesAsync(WorkflowDefinitionFilter filter, WorkflowDefinitionOrder order, CancellationToken cancellationToken = default) { - var result = _store.Query(query => Filter(query, filter).OrderBy(order)).Select(WorkflowDefinitionSummary.FromDefinition).ToList().AsEnumerable(); + var result = store.Query(query => Filter(query, filter).OrderBy(order)).Select(WorkflowDefinitionSummary.FromDefinition).ToList().AsEnumerable(); return Task.FromResult(result); } /// public Task FindLastVersionAsync(WorkflowDefinitionFilter filter, CancellationToken cancellationToken) { - var result = _store.Query(query => Filter(query, filter)).MaxBy(x => x.Version); + var result = store.Query(query => Filter(query, filter)).MaxBy(x => x.Version); return Task.FromResult(result); } /// public Task SaveAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { - _store.Save(definition, GetId); + store.Save(definition, GetId); return Task.CompletedTask; } /// public Task SaveManyAsync(IEnumerable definitions, CancellationToken cancellationToken = default) { - _store.SaveMany(definitions, GetId); + store.SaveMany(definitions, GetId); return Task.CompletedTask; } /// public Task DeleteAsync(WorkflowDefinitionFilter filter, CancellationToken cancellationToken = default) { - var workflowDefinitionIds = _store.Query(query => Filter(query, filter)).Select(x => x.DefinitionId).Distinct().ToList(); - _store.DeleteWhere(x => workflowDefinitionIds.Contains(x.DefinitionId)); + var workflowDefinitionIds = store.Query(query => Filter(query, filter)).Select(x => x.DefinitionId).Distinct().ToList(); + store.DeleteWhere(x => workflowDefinitionIds.Contains(x.DefinitionId)); return Task.FromResult(workflowDefinitionIds.LongCount()); } /// public Task AnyAsync(WorkflowDefinitionFilter filter, CancellationToken cancellationToken = default) { - var exists = _store.Query(query => Filter(query, filter)).Any(); + var exists = store.Query(query => Filter(query, filter)).Any(); return Task.FromResult(exists); } /// public Task CountDistinctAsync(CancellationToken cancellationToken = default) { - return Task.FromResult(_store.Count(x => true, x => x.DefinitionId)); + return Task.FromResult(store.Count(x => true, x => x.DefinitionId)); } /// public Task GetIsNameUnique(string name, string? definitionId = default, CancellationToken cancellationToken = default) { - var exists = _store.Any(x => x.Name == name && x.DefinitionId != definitionId); + var exists = store.Any(x => x.Name == name && x.DefinitionId != definitionId); return Task.FromResult(!exists); } diff --git a/src/modules/Elsa.Workflows.Runtime/Exceptions/WorkflowGraphNotFoundException.cs b/src/modules/Elsa.Workflows.Runtime/Exceptions/WorkflowGraphNotFoundException.cs new file mode 100644 index 000000000..1d0f302fe --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Exceptions/WorkflowGraphNotFoundException.cs @@ -0,0 +1,8 @@ +using Elsa.Workflows.Models; + +namespace Elsa.Workflows.Runtime.Exceptions; + +public class WorkflowGraphNotFoundException(string message, WorkflowDefinitionHandle workflowDefinitionHandle) : Exception(message) +{ + public WorkflowDefinitionHandle WorkflowDefinitionHandle { get; } = workflowDefinitionHandle; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index 7f9d301d4..5bf4f976d 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -31,7 +31,6 @@ namespace Elsa.Workflows.Runtime.Features; /// Installs and configures workflow runtime features. /// [DependsOn(typeof(SystemClockFeature))] -[DependsOn(typeof(BackgroundTasksFeature))] public class WorkflowRuntimeFeature : FeatureBase { /// diff --git a/src/modules/Elsa.Workflows.Runtime/Filters/BookmarkFilter.cs b/src/modules/Elsa.Workflows.Runtime/Filters/BookmarkFilter.cs index 3b43f2128..79823840b 100644 --- a/src/modules/Elsa.Workflows.Runtime/Filters/BookmarkFilter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Filters/BookmarkFilter.cs @@ -81,4 +81,9 @@ public class BookmarkFilter return query; } -} + + public static BookmarkFilter ByActivityTypeNames(IEnumerable activityTypeNames) => new() + { + ActivityTypeNames = activityTypeNames.ToList() + }; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Filters/TriggerFilter.cs b/src/modules/Elsa.Workflows.Runtime/Filters/TriggerFilter.cs index a93b7573e..cab0d7536 100644 --- a/src/modules/Elsa.Workflows.Runtime/Filters/TriggerFilter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Filters/TriggerFilter.cs @@ -73,4 +73,9 @@ public class TriggerFilter if (Hash != null) queryable = queryable.Where(x => x.Hash == Hash); return queryable; } -} + + public static TriggerFilter ByNames(IEnumerable names) => new() + { + Names = names.ToList() + }; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs index 47d974760..11d861160 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs @@ -1,6 +1,7 @@ using Elsa.Workflows.Activities; using Elsa.Workflows.Management; using Elsa.Workflows.Models; +using Elsa.Workflows.Runtime.Exceptions; using Elsa.Workflows.Runtime.Messages; namespace Elsa.Workflows.Runtime; @@ -55,7 +56,7 @@ public class DefaultWorkflowStarter(IWorkflowDefinitionService workflowDefinitio var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(request.WorkflowDefinitionHandle, cancellationToken); if (workflowGraph == null) - throw new InvalidOperationException($"Workflow definition with handle {request.WorkflowDefinitionHandle} was not found."); + throw new WorkflowGraphNotFoundException($"Workflow definition not found.", request.WorkflowDefinitionHandle); return workflowGraph.Workflow; }