Skip to content

Multitenancy #8329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 96 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
efc39f7
Initial configuration
nolanpro Apr 25, 2025
c41c178
Handle storage and config for tenants
nolanpro May 23, 2025
54b8ca4
Add artisan helper
nolanpro May 23, 2025
c79ab5e
Update switch tenant command
nolanpro May 29, 2025
4ee39ef
Add multitenancy commands
nolanpro May 30, 2025
2f4c86b
Use metrics service container instead of facade root
nolanpro Jun 5, 2025
d1c12fb
Remove unused file
nolanpro Jun 5, 2025
f505ff5
Reset services that use redis after switch
nolanpro Jun 5, 2025
aefbe11
feat: add tenant storage link command
devmiguelangel Jun 5, 2025
33431ed
feat: implement custom media URL generator
devmiguelangel Jun 5, 2025
7c6fdb1
Allow configurable db username/password
nolanpro Jun 5, 2025
e4db84d
Add list command
nolanpro Jun 5, 2025
33f0588
Additional support for console commands
nolanpro Jun 5, 2025
1ff72eb
Merge remote-tracking branch 'origin/poc/FOUR-22888-Multitenancy' int…
devmiguelangel Jun 6, 2025
8b1ef90
Remove debugging
nolanpro Jun 6, 2025
ef6252a
feat: enhance multitenancy support in CSS compilation
devmiguelangel Jun 9, 2025
9a7c786
feat: implement tenant-specific CSS path helper and update CSS
devmiguelangel Jun 9, 2025
b14f001
feat: update configurations
devmiguelangel Jun 9, 2025
6caaf86
feat: add spatie/laravel-multitenancy to composer.lock
devmiguelangel Jun 9, 2025
bdb4835
Update cache prefix strategy
nolanpro Jun 9, 2025
356a1ed
feat: enhance cache prefixing with tenant ID support
devmiguelangel Jun 9, 2025
0998cad
Merge remote-tracking branch 'origin/poc/FOUR-22888-Multitenancy' int…
devmiguelangel Jun 9, 2025
10a0170
refactor: improve tenant url generation
devmiguelangel Jun 9, 2025
7898862
Update MultitenancyCreate.php
nolanpro Jun 10, 2025
d1bbd97
fix trying to use an undefined constant
rodriquelca Jun 10, 2025
40e9a81
update condition
rodriquelca Jun 10, 2025
5b15f66
Merge pull request #8311 from ProcessMaker/bugfix/exist
nolanpro Jun 10, 2025
d07f7ab
feat: implement tenant cache prefixing for settings
devmiguelangel Jun 10, 2025
7e4a034
refactor: streamline tenant storage link command and improve context …
devmiguelangel Jun 10, 2025
bb5d6c7
Move commands under 'tenants' prefix
nolanpro Jun 10, 2025
57f446f
feat: add MixServiceProvider for tenant asset handling
devmiguelangel Jun 10, 2025
4d273be
Merge remote-tracking branch 'origin/poc/FOUR-22888-Multitenancy' int…
devmiguelangel Jun 10, 2025
494b691
feat: implement caching for mix manifest
devmiguelangel Jun 10, 2025
bafa79f
Add tenants transition command
nolanpro Jun 10, 2025
337bf39
Merge remote-tracking branch 'origin/poc/FOUR-22888-Multitenancy' int…
devmiguelangel Jun 11, 2025
4544f4d
Merge pull request #8308 from ProcessMaker/FOUR-24648
nolanpro Jun 11, 2025
5565385
fix space
nolanpro Jun 11, 2025
d3ee0a4
Update tenant create
nolanpro Jun 11, 2025
d3e81b9
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jun 11, 2025
27e75d8
Set spatie multitenancy version
nolanpro Jun 11, 2025
551e7d1
Update tenants enable
nolanpro Jun 12, 2025
d0546b5
Update tenants commands
nolanpro Jun 12, 2025
e0e2e70
FOUR-24647: Scheduled tasks
rodriquelca Jun 12, 2025
76c1395
fix code style
rodriquelca Jun 12, 2025
8ea9870
restore comand kernel.php
rodriquelca Jun 12, 2025
40fd1c5
feat: add storage controller for tenant file handling
devmiguelangel Jun 12, 2025
bfd19e4
feat: improve reliability for file serving and tenant path handling
devmiguelangel Jun 12, 2025
220f73e
improve the registerScheduledTasksForTenant using spatie get tenant s…
rodriquelca Jun 12, 2025
d3b6e1f
refactor: remove TenantStorageMiddleware
devmiguelangel Jun 13, 2025
6ce4f81
refactor: enhance tenant handling
devmiguelangel Jun 13, 2025
652520c
Merge pull request #8319 from ProcessMaker/FOUR-24645
nolanpro Jun 16, 2025
2b25f10
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jun 16, 2025
2eca5f9
feat: add tracking tenant job service provider
devmiguelangel Jun 17, 2025
d65da25
Add currentTenant singleton
nolanpro Jun 17, 2025
496aa7d
feat: implement tenant queues dashboard
devmiguelangel Jun 17, 2025
b028386
refactor: update routes
devmiguelangel Jun 17, 2025
cc01b37
TenantsArtisanCommand was removed instead RegisterTenantScheduleTasks…
rodriquelca Jun 17, 2025
d139d33
clean code
rodriquelca Jun 17, 2025
71d52f1
clean code 2
rodriquelca Jun 17, 2025
afb9ba1
feat: update job status handling
devmiguelangel Jun 18, 2025
e08c070
feat: enhance tenant job data handling and update UI components
devmiguelangel Jun 18, 2025
34d1403
feat: implement tenant job tracking configuration
devmiguelangel Jun 19, 2025
2b8d0a3
Fix jobs for non-multitenancy instances
nolanpro Jun 23, 2025
359bf33
Original db config for non-multitenancy instances
nolanpro Jun 23, 2025
54baa01
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jun 27, 2025
99b343f
Merge pull request #8316 from ProcessMaker/feature/FOUR-24647
nolanpro Jun 30, 2025
67d5c36
Merge pull request #8330 from ProcessMaker/FOUR-24655
nolanpro Jun 30, 2025
70273b6
Original db config for non-multitenant environment
nolanpro Jun 30, 2025
70f8235
Remove console check
nolanpro Jul 1, 2025
e8bfed2
Fix tenants command
nolanpro Jul 1, 2025
6a09cbf
Update tenants enable command
nolanpro Jul 2, 2025
b5e00ed
Specify database
nolanpro Jul 2, 2025
c85f71f
feat: enhance Translation model for multitenant support
devmiguelangel Jul 2, 2025
dbcb91c
Add missing tenant error page
nolanpro Jul 3, 2025
8dc3f88
Update tenants create
nolanpro Jul 3, 2025
df7c42c
Fix custom UI and broadcast settings
nolanpro Jul 11, 2025
50083aa
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jul 11, 2025
1fae91d
Remove artisan in dev
nolanpro Jul 11, 2025
4c7b03e
Add a landlord landing page
nolanpro Jul 11, 2025
9b33269
Fix config path
nolanpro Jul 11, 2025
2be1294
Fix events
nolanpro Jul 11, 2025
a7bbbaa
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jul 14, 2025
8b1835b
Update error handling for multitenancy
nolanpro Jul 15, 2025
79e71e4
Make script runner callback tenant aware
nolanpro Jul 15, 2025
6375992
Fix originalCache in switch tenant
nolanpro Jul 16, 2025
7eae34c
Resolve deferred provider first
nolanpro Jul 17, 2025
ab7d844
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jul 17, 2025
6d60e05
Merge branch 'poc/FOUR-22888-Multitenancy' into FOUR-24659
nolanpro Jul 17, 2025
0ff5bae
Merge pull request #8339 from ProcessMaker/FOUR-24659
nolanpro Jul 17, 2025
e53c0fa
Call define gates after auth boot in tests
nolanpro Jul 18, 2025
e0e9540
Fix tests for multitenancy
nolanpro Jul 18, 2025
1d87125
Segemnt horizon queues per tenant
nolanpro Jul 22, 2025
5d111d0
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jul 22, 2025
3cbe4d8
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jul 25, 2025
34e175f
Update tenant artisan commands
nolanpro Jul 25, 2025
abc41ac
Merge branch 'develop' into poc/FOUR-22888-Multitenancy
nolanpro Jul 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pm4.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ on:
jobs:
run:
name: Run PM4-workflow
uses: processmaker/.github/.github/workflows/deploy-pm4.yml@main
uses: processmaker/.github/.github/workflows/deploy-pm4.yml@multitenancy
secrets: inherit
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/public/css
/storage/*.key
/storage/*.index
/storage/tenant_*
/vendor
/.idea
/.vscode
Expand Down Expand Up @@ -44,5 +45,9 @@ coverage
resources/lang/de
resources/lang/es
resources/lang/fr
resources/sass/tenant_*
resources/sass/_variables_bk.scss
devhub/pm-font/dist
test-db-snapshot.db
test-db-snapshot.db
snapshot_*.db
storage/transitions
10 changes: 10 additions & 0 deletions ProcessMaker/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,14 @@ public function path($path = '')
{
return $this->basePath . DIRECTORY_SEPARATOR . 'ProcessMaker' . ($path ? DIRECTORY_SEPARATOR . $path : $path);
}

public function setStoragePath($path)
{
$this->storagePath = $path;
}

public function getStoragePath()
{
return $this->storagePath;
}
}
3 changes: 2 additions & 1 deletion ProcessMaker/Cache/Monitoring/PrometheusMetricsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Support\Facades\Redis;
use ProcessMaker\Facades\Metrics;
use ProcessMaker\Services\MetricsService;
use Prometheus\CollectorRegistry;

class PrometheusMetricsManager implements CacheMetricsInterface
Expand All @@ -25,7 +26,7 @@ class PrometheusMetricsManager implements CacheMetricsInterface
*/
public function __construct(string $namespace = 'cache')
{
$this->metrics = Metrics::getFacadeRoot();
$this->metrics = app(MetricsService::class);
$this->namespace = $namespace;
}

Expand Down
2 changes: 1 addition & 1 deletion ProcessMaker/Console/Commands/GenerateSdk.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function __construct()
*/
public function handle()
{
$jsonPath = base_path('storage/api-docs/api-docs.json');
$jsonPath = storage_path('api-docs/api-docs.json');
$builder = new BuildSdk($jsonPath, $this->argument('output'));

$userId = $this->options()['user-id'];
Expand Down
71 changes: 2 additions & 69 deletions ProcessMaker/Console/Commands/RegenerateCss.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use ProcessMaker\Jobs\CompileSass;
use ProcessMaker\Jobs\CompileUI;
use ProcessMaker\Models\Setting;
use ProcessMaker\PackageChecker;

Expand Down Expand Up @@ -44,75 +45,7 @@ public function __construct()
public function handle()
{
$this->info("\nStarting CSS compiling...");

$setting = Setting::byKey('css-override');

if ($setting) {
$this->writeColors(json_decode($setting->attributesToArray()['config']['variables'], true));
$this->writeFonts(json_decode($setting->attributesToArray()['config']['sansSerifFont']));
}

// Compile the Sass files
CompileSass::dispatch([
'tag' => 'sidebar',
'origin' => 'resources/sass/sidebar/sidebar.scss',
'target' => 'public/css/sidebar.css',
'user' => null,
]);

CompileSass::dispatch([
'tag' => 'app',
'origin' => 'resources/sass/app.scss',
'target' => 'public/css/app.css',
'user' => null,
]);

CompileSass::dispatch([
'tag' => 'queues',
'origin' => 'resources/sass/admin/queues.scss',
'target' => 'public/css/admin/queues.css',
'user' => null,
]);

(new CompileUI())->handle();
$this->info("\nCSS files have been generated.");
}

/**
* Write variables in file
*
* @param $request
*/
private function writeColors($data)
{
// Now generate the _colors.scss file
$contents = "// Changed theme colors\n";
foreach ($data as $key => $value) {
$contents .= $value['id'] . ': ' . $value['value'] . ";\n";
}
File::put(app()->resourcePath('sass') . '/_colors.scss', $contents);
}

/**
* Write variables font in file
*
* @param $sansSerif
* @param $serif
*/
private function writeFonts($sansSerif)
{
$sansSerif = $sansSerif ? $sansSerif : $this->sansSerifFontDefault();
// Generate the _fonts.scss file
$contents = "// Changed theme fonts\n";
$contents .= '$font-family-sans-serif: ' . $sansSerif->id . " !default;\n";
File::put(app()->resourcePath('sass') . '/_fonts.scss', $contents);
}

private function sansSerifFontDefault()
{
$data = new \stdClass();
$data->id = "'Open Sans'";
$data->title = "'Open Sans'";

return $data;
}
}
108 changes: 108 additions & 0 deletions ProcessMaker/Console/Commands/TenantStorageLink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace ProcessMaker\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class TenantStorageLink extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tenants:storage-link';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Create the symbolic link for the current tenant storage directory';

/**
* Execute the console command.
*/
public function handle()
{
$tenant = app('currentTenant');

if (!$tenant) {
$this->error('This command must be run within a tenant context using tenants:artisan');

return 1;
}

$this->createStorageLink($tenant->id);
}

/**
* Create symbolic link for the current tenant
*
* @param int $tenantId
* @return void
*/
protected function createStorageLink($tenantId)
{
$this->info('Creating storage link for tenant ' . $tenantId . '...');

// Define paths
$tenantStoragePath = storage_path('/app/public');
$publicPath = public_path('storage/tenant_' . $tenantId);

// Create tenant storage directory if it doesn't exist
if (!File::exists($tenantStoragePath)) {
$this->info('Creating tenant storage directory: ' . $tenantStoragePath);
File::makeDirectory($tenantStoragePath, 0755, true);
}

// Create parent directory for the symbolic link if it doesn't exist
$parentDir = dirname($publicPath);
if (!File::exists($parentDir)) {
$this->info('Creating parent directory: ' . $parentDir);
File::makeDirectory($parentDir, 0755, true);
}

// Remove existing link or directory if it exists
if (File::exists($publicPath)) {
// We can't use `confirm` here because this command will be run without any user interaction
// if (!$this->confirm('The symbolic link for tenant ' . $tenantId . ' already exists. Do you want to recreate it?')) {
// return;
// }
if (is_link($publicPath)) {
File::delete($publicPath);
} else {
File::deleteDirectory($publicPath);
}
}

try {
// Create the symbolic link
$this->info('Creating symbolic link from ' . $tenantStoragePath . ' to ' . $publicPath);

// Use absolute paths for symlink
$absoluteStoragePath = realpath($tenantStoragePath);
if (!$absoluteStoragePath) {
throw new \Exception('Could not resolve absolute path for: ' . $tenantStoragePath);
}

if (!symlink($absoluteStoragePath, $publicPath)) {
throw new \Exception('Failed to create symbolic link. Check permissions and paths.');
}

$this->info("Symbolic link for tenant {$tenantId} created successfully.");
} catch (\Exception $e) {
$this->error('Could not create symbolic link for tenant ' . $tenantId . ': ' . $e->getMessage());
$this->error('Source path: ' . $tenantStoragePath);
$this->error('Target path: ' . $publicPath);

// Additional debugging information
$this->info("\nDebugging information:");
$this->info('Source exists: ' . (File::exists($tenantStoragePath) ? 'Yes' : 'No'));
$this->info('Source is readable: ' . (is_readable($tenantStoragePath) ? 'Yes' : 'No'));
$this->info('Parent directory exists: ' . (File::exists($parentDir) ? 'Yes' : 'No'));
$this->info('Parent directory is writable: ' . (is_writable($parentDir) ? 'Yes' : 'No'));
}
}
}
109 changes: 109 additions & 0 deletions ProcessMaker/Console/Commands/TenantsAddScheduleTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace ProcessMaker\Console\Commands;

use Illuminate\Console\Command;
use ProcessMaker\Multitenancy\Tenant;

class TenantsAddScheduleTask extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tenants:add-schedule-task
{--tenant= : The ID of the tenant}
{--command= : The command to schedule}
{--frequency=daily : The frequency to run the command (e.g., everyMinute, hourly, daily, weekly, monthly, custom)}
{--cron= : Custom cron expression (required if frequency is custom)}
{--options= : Comma-separated list of options (withoutOverlapping, onOneServer)}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Add a scheduled task to a tenant configuration';

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$tenantId = $this->option('tenant');
$command = $this->option('command');
$frequency = $this->option('frequency');
$cron = $this->option('cron');
$optionsString = $this->option('options');

// Validate required parameters
if (!$tenantId) {
$this->error('Tenant ID is required.');

return 1;
}

if (!$command) {
$this->error('Command is required.');

return 1;
}

if ($frequency === 'custom' && !$cron) {
$this->error('Cron expression is required when frequency is custom.');

return 1;
}

// Find the tenant
$tenant = Tenant::find($tenantId);
if (!$tenant) {
$this->error("Tenant with ID {$tenantId} not found.");

return 1;
}

// Get current config or initialize empty array
$config = $tenant->config ?? [];

// Initialize schedule config if not exists
if (!isset($config['schedule'])) {
$config['schedule'] = [];
}

// Set up the task configuration
$taskConfig = [
'frequency' => $frequency,
];

// Add cron expression if using custom frequency
if ($frequency === 'custom') {
$taskConfig['cron'] = $cron;
}

// Process options
if ($optionsString) {
$options = explode(',', $optionsString);
foreach ($options as $option) {
$option = trim($option);
if ($option === 'withoutOverlapping' || $option === 'onOneServer') {
$taskConfig[$option] = true;
}
}
}

// Add or update the command in the tenant's schedule config
$config['schedule'][$command] = $taskConfig;

// Save the updated configuration
$tenant->config = $config;
$tenant->save();

$this->info("Scheduled task '{$command}' added to tenant {$tenantId} with frequency '{$frequency}'.");

return 0;
}
}
Loading
Loading