mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 14:14:04 +00:00
Merge branch 'master' of github.com:archtechx/tenancy
This commit is contained in:
commit
e806825f71
6 changed files with 128 additions and 47 deletions
|
|
@ -51,13 +51,24 @@ class Migrate extends MigrateCommand
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->getProcesses() > 1) {
|
$originalTemplateConnection = config('tenancy.database.template_tenant_connection');
|
||||||
return $this->runConcurrently($this->getTenantChunks()->map(function ($chunk) {
|
|
||||||
return $this->getTenants($chunk);
|
if ($database = $this->input->getOption('database')) {
|
||||||
}));
|
config(['tenancy.database.template_tenant_connection' => $database]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->migrateTenants($this->getTenants()) ? 0 : 1;
|
if ($this->getProcesses() > 1) {
|
||||||
|
$code = $this->runConcurrently($this->getTenantChunks()->map(function ($chunk) {
|
||||||
|
return $this->getTenants($chunk);
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
$code = $this->migrateTenants($this->getTenants()) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the template tenant connection to the original one
|
||||||
|
config(['tenancy.database.template_tenant_connection' => $originalTemplateConnection]);
|
||||||
|
|
||||||
|
return $code;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function childHandle(mixed ...$args): bool
|
protected function childHandle(mixed ...$args): bool
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Commands;
|
namespace Stancl\Tenancy\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\ConfirmableTrait;
|
||||||
use Illuminate\Database\Console\Migrations\BaseCommand;
|
use Illuminate\Database\Console\Migrations\BaseCommand;
|
||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Illuminate\Support\LazyCollection;
|
use Illuminate\Support\LazyCollection;
|
||||||
|
|
@ -17,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface as OI;
|
||||||
|
|
||||||
class MigrateFresh extends BaseCommand
|
class MigrateFresh extends BaseCommand
|
||||||
{
|
{
|
||||||
use HasTenantOptions, DealsWithMigrations, ParallelCommand;
|
use HasTenantOptions, DealsWithMigrations, ParallelCommand, ConfirmableTrait;
|
||||||
|
|
||||||
protected $description = 'Drop all tables and re-run all migrations for tenant(s)';
|
protected $description = 'Drop all tables and re-run all migrations for tenant(s)';
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ class MigrateFresh extends BaseCommand
|
||||||
|
|
||||||
$this->addOption('drop-views', null, InputOption::VALUE_NONE, 'Drop views along with tenant tables.', null);
|
$this->addOption('drop-views', null, InputOption::VALUE_NONE, 'Drop views along with tenant tables.', null);
|
||||||
$this->addOption('step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually.');
|
$this->addOption('step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually.');
|
||||||
|
$this->addOption('force', null, InputOption::VALUE_NONE, 'Force the command to run when in production.', null);
|
||||||
$this->addProcessesOption();
|
$this->addProcessesOption();
|
||||||
|
|
||||||
$this->setName('tenants:migrate-fresh');
|
$this->setName('tenants:migrate-fresh');
|
||||||
|
|
@ -34,6 +36,10 @@ class MigrateFresh extends BaseCommand
|
||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
|
if (! $this->confirmToProceed()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
$success = true;
|
$success = true;
|
||||||
|
|
||||||
if ($this->getProcesses() > 1) {
|
if ($this->getProcesses() > 1) {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Features;
|
namespace Stancl\Tenancy\Features;
|
||||||
|
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Support\Facades\Vite;
|
||||||
use Stancl\Tenancy\Contracts\Feature;
|
use Stancl\Tenancy\Contracts\Feature;
|
||||||
use Stancl\Tenancy\Overrides\Vite;
|
|
||||||
use Stancl\Tenancy\Tenancy;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class ViteBundler implements Feature
|
class ViteBundler implements Feature
|
||||||
|
|
@ -21,6 +21,8 @@ class ViteBundler implements Feature
|
||||||
|
|
||||||
public function bootstrap(Tenancy $tenancy): void
|
public function bootstrap(Tenancy $tenancy): void
|
||||||
{
|
{
|
||||||
$this->app->singleton(\Illuminate\Foundation\Vite::class, Vite::class);
|
Vite::createAssetPathsUsing(function ($path, $secure = null) {
|
||||||
|
return global_asset($path);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Overrides;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Vite as BaseVite;
|
|
||||||
|
|
||||||
class Vite extends BaseVite
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Generate an asset path for the application.
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @param bool|null $secure
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function assetPath($path, $secure = null)
|
|
||||||
{
|
|
||||||
return global_asset($path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,6 +27,7 @@ use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
|
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
|
||||||
use function Stancl\Tenancy\Tests\pest;
|
use function Stancl\Tenancy\Tests\pest;
|
||||||
|
use Stancl\Tenancy\Events\MigratingDatabase;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) {
|
if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) {
|
||||||
|
|
@ -95,6 +96,60 @@ test('migrate command works with tenants option', function () {
|
||||||
expect(Schema::hasTable('users'))->toBeTrue();
|
expect(Schema::hasTable('users'))->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('migrate command uses the passed database option as the template tenant connection', function () {
|
||||||
|
$originalTemplateConnection = config('tenancy.database.template_tenant_connection');
|
||||||
|
|
||||||
|
// Add a custom connection that will be used as the template for the tenant connection
|
||||||
|
// Identical to the default (mysql), just with different charset and collation
|
||||||
|
config(['database.connections.custom_connection' => [
|
||||||
|
"driver" => "mysql",
|
||||||
|
"url" => "",
|
||||||
|
"host" => "mysql",
|
||||||
|
"port" => "3306",
|
||||||
|
"database" => "main",
|
||||||
|
"username" => "root",
|
||||||
|
"password" => "password",
|
||||||
|
"unix_socket" => "",
|
||||||
|
"charset" => "latin1", // Different from the default (utf8mb4)
|
||||||
|
"collation" => "latin1_swedish_ci", // Different from the default (utf8mb4_unicode_ci)
|
||||||
|
"prefix" => "",
|
||||||
|
"prefix_indexes" => true,
|
||||||
|
"strict" => true,
|
||||||
|
"engine" => null,
|
||||||
|
"options" => []
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$templateConnectionDuringMigration = null;
|
||||||
|
$tenantConnectionDuringMigration = null;
|
||||||
|
|
||||||
|
Event::listen(MigratingDatabase::class, function() use (&$templateConnectionDuringMigration, &$tenantConnectionDuringMigration) {
|
||||||
|
$templateConnectionDuringMigration = config('tenancy.database.template_tenant_connection');
|
||||||
|
$tenantConnectionDuringMigration = DB::connection('tenant')->getConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
// The original tenant template connection config remains default
|
||||||
|
expect(config('tenancy.database.template_tenant_connection'))->toBe($originalTemplateConnection);
|
||||||
|
|
||||||
|
Tenant::create();
|
||||||
|
|
||||||
|
// The original template connection is used when the --database option is not passed
|
||||||
|
pest()->artisan('tenants:migrate');
|
||||||
|
expect($templateConnectionDuringMigration)->toBe($originalTemplateConnection);
|
||||||
|
|
||||||
|
Tenant::create();
|
||||||
|
|
||||||
|
// The migrate command temporarily uses the connection passed in the --database option
|
||||||
|
pest()->artisan('tenants:migrate', ['--database' => 'custom_connection']);
|
||||||
|
expect($templateConnectionDuringMigration)->toBe('custom_connection');
|
||||||
|
|
||||||
|
// The tenant connection during migration actually used custom_connection's config
|
||||||
|
expect($tenantConnectionDuringMigration['charset'])->toBe('latin1');
|
||||||
|
expect($tenantConnectionDuringMigration['collation'])->toBe('latin1_swedish_ci');
|
||||||
|
|
||||||
|
// The tenant template connection config is restored to the original after migrating
|
||||||
|
expect(config('tenancy.database.template_tenant_connection'))->toBe($originalTemplateConnection);
|
||||||
|
});
|
||||||
|
|
||||||
test('migrate command only throws exceptions if skip-failing is not passed', function() {
|
test('migrate command only throws exceptions if skip-failing is not passed', function() {
|
||||||
Tenant::create();
|
Tenant::create();
|
||||||
|
|
||||||
|
|
@ -311,6 +366,21 @@ test('migrate fresh command works', function () {
|
||||||
expect(DB::table('users')->exists())->toBeFalse();
|
expect(DB::table('users')->exists())->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('migrate fresh command respects force option in production', function () {
|
||||||
|
// Set environment to production
|
||||||
|
app()->detectEnvironment(fn() => 'production');
|
||||||
|
|
||||||
|
Tenant::create();
|
||||||
|
|
||||||
|
// Without --force in production, command should prompt for confirmation
|
||||||
|
pest()->artisan('tenants:migrate-fresh')
|
||||||
|
->expectsConfirmation('Are you sure you want to run this command?');
|
||||||
|
|
||||||
|
// With --force, command should succeed without prompting
|
||||||
|
pest()->artisan('tenants:migrate-fresh', ['--force' => true])
|
||||||
|
->assertSuccessful();
|
||||||
|
});
|
||||||
|
|
||||||
test('run command with array of tenants works', function () {
|
test('run command with array of tenants works', function () {
|
||||||
$tenantId1 = Tenant::create()->getTenantKey();
|
$tenantId1 = Tenant::create()->getTenantKey();
|
||||||
$tenantId2 = Tenant::create()->getTenantKey();
|
$tenantId2 = Tenant::create()->getTenantKey();
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,41 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Foundation\Vite;
|
use Illuminate\Foundation\Vite;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Illuminate\Support\Facades\File;
|
||||||
use Stancl\Tenancy\Overrides\Vite as StanclVite;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Features\ViteBundler;
|
use Stancl\Tenancy\Features\ViteBundler;
|
||||||
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
|
|
||||||
test('vite helper uses our custom class', function() {
|
use function Stancl\Tenancy\Tests\withBootstrapping;
|
||||||
$vite = app(Vite::class);
|
|
||||||
|
|
||||||
expect($vite)->toBeInstanceOf(Vite::class);
|
|
||||||
expect($vite)->not()->toBeInstanceOf(StanclVite::class);
|
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
config([
|
config([
|
||||||
'tenancy.features' => [ViteBundler::class],
|
'tenancy.filesystem.asset_helper_override' => true,
|
||||||
|
'tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$tenant = Tenant::create();
|
File::ensureDirectoryExists(dirname($manifestPath = public_path('build/manifest.json')));
|
||||||
|
File::put($manifestPath, json_encode([
|
||||||
tenancy()->initialize($tenant);
|
'foo' => [
|
||||||
|
'file' => 'assets/foo-AbC123.js',
|
||||||
app()->forgetInstance(Vite::class);
|
'src' => 'js/foo.js',
|
||||||
|
],
|
||||||
$vite = app(Vite::class);
|
]));
|
||||||
|
});
|
||||||
expect($vite)->toBeInstanceOf(StanclVite::class);
|
|
||||||
|
test('vite bundler ensures vite assets use global_asset when asset_helper_override is enabled', function () {
|
||||||
|
config(['tenancy.features' => [ViteBundler::class]]);
|
||||||
|
|
||||||
|
withBootstrapping();
|
||||||
|
|
||||||
|
tenancy()->initialize(Tenant::create());
|
||||||
|
|
||||||
|
// Not what we want
|
||||||
|
expect(asset('foo'))->toBe(route('stancl.tenancy.asset', ['path' => 'foo']));
|
||||||
|
|
||||||
|
$viteAssetUrl = app(Vite::class)->asset('foo');
|
||||||
|
$expectedGlobalUrl = global_asset('build/assets/foo-AbC123.js');
|
||||||
|
|
||||||
|
expect($viteAssetUrl)->toBe($expectedGlobalUrl);
|
||||||
|
expect($viteAssetUrl)->toBe('http://localhost/build/assets/foo-AbC123.js');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue