|
|
@@ -0,0 +1,253 @@
|
|
|
+<?php
|
|
|
+namespace App\Services;
|
|
|
+
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use Illuminate\Support\Facades\File;
|
|
|
+use Illuminate\Support\Facades\Artisan;
|
|
|
+use App\Models\User;
|
|
|
+use App\Database\Migrations\MasterMigration;
|
|
|
+use App\Database\Migrations\TenantMigration;
|
|
|
+
|
|
|
+class MigrationService
|
|
|
+{
|
|
|
+ protected $masterDatabase = 'easia_tenant';
|
|
|
+
|
|
|
+ public function runMasterMigrations()
|
|
|
+ {
|
|
|
+
|
|
|
+ $this->setupMasterConnection();
|
|
|
+
|
|
|
+ $masterMigrations = $this->getMasterMigrationFiles();
|
|
|
+
|
|
|
+ if (empty($masterMigrations)) {
|
|
|
+ return ['message' => 'No master migrations found.', 'migrations' => []];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a temporary directory for master migrations only
|
|
|
+ $tempPath = storage_path('app/temp_master_migrations');
|
|
|
+ if (!File::exists($tempPath)) {
|
|
|
+ File::makeDirectory($tempPath, 0755, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy only master migrations to temp directory
|
|
|
+ foreach ($masterMigrations as $migration) {
|
|
|
+ $source = database_path('migrations/' . $migration);
|
|
|
+ $destination = $tempPath . '/' . $migration;
|
|
|
+ File::copy($source, $destination);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Run migrations from temp directory
|
|
|
+ Artisan::call('migrate', [
|
|
|
+ '--database' => 'master',
|
|
|
+ '--path' => 'storage/app/temp_master_migrations'
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $output = Artisan::output();
|
|
|
+ } finally {
|
|
|
+ // Clean up temp directory
|
|
|
+ File::deleteDirectory($tempPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'message' => 'Master migrations completed successfully!',
|
|
|
+ 'database' => $this->masterDatabase,
|
|
|
+ 'migrations' => $masterMigrations,
|
|
|
+ 'output' => $output
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function runTenantMigrations()
|
|
|
+ {
|
|
|
+ $tenants = $this->getTenants();
|
|
|
+ $results = [];
|
|
|
+
|
|
|
+ if ($tenants->isEmpty()) {
|
|
|
+ return ['message' => 'No tenants found.', 'results' => []];
|
|
|
+ }
|
|
|
+
|
|
|
+ $tenantMigrations = $this->getTenantMigrationFiles();
|
|
|
+
|
|
|
+ if (empty($tenantMigrations)) {
|
|
|
+ return ['message' => 'No tenant migrations found.', 'results' => []];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a temporary directory for tenant migrations only
|
|
|
+ $tempPath = storage_path('app/temp_tenant_migrations');
|
|
|
+ if (!File::exists($tempPath)) {
|
|
|
+ File::makeDirectory($tempPath, 0755, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy only tenant migrations to temp directory
|
|
|
+ foreach ($tenantMigrations as $migration) {
|
|
|
+ $source = database_path('migrations/' . $migration);
|
|
|
+ $destination = $tempPath . '/' . $migration;
|
|
|
+ File::copy($source, $destination);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($tenants as $tenant) {
|
|
|
+ try {
|
|
|
+ $this->setupTenantConnection($tenant);
|
|
|
+
|
|
|
+ Artisan::call('migrate', [
|
|
|
+ '--database' => 'tenant',
|
|
|
+ '--path' => 'storage/app/temp_tenant_migrations'
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $results[] = [
|
|
|
+ 'tenant' => $tenant->tenant_database,
|
|
|
+ 'status' => 'success',
|
|
|
+ 'message' => 'Migrations completed successfully'
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $results[] = [
|
|
|
+ 'tenant' => $tenant->tenant_database,
|
|
|
+ 'status' => 'error',
|
|
|
+ 'message' => $e->getMessage()
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clean up temp directory
|
|
|
+ File::deleteDirectory($tempPath);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'message' => 'Tenant migrations process completed',
|
|
|
+ 'results' => $results
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function rollbackMasterMigrations($steps = 1)
|
|
|
+ {
|
|
|
+ $this->setupMasterConnection();
|
|
|
+
|
|
|
+ Artisan::call('migrate:rollback', [
|
|
|
+ '--database' => 'master',
|
|
|
+ '--step' => $steps
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'message' => "Master database rolled back {$steps} step(s)",
|
|
|
+ 'database' => $this->masterDatabase,
|
|
|
+ 'output' => Artisan::output()
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function rollbackTenantMigrations($steps = 1)
|
|
|
+ {
|
|
|
+ $tenants = $this->getTenants();
|
|
|
+ $results = [];
|
|
|
+
|
|
|
+ foreach ($tenants as $tenant) {
|
|
|
+ try {
|
|
|
+ $this->setupTenantConnection($tenant);
|
|
|
+
|
|
|
+ Artisan::call('migrate:rollback', [
|
|
|
+ '--database' => 'tenant',
|
|
|
+ '--step' => $steps
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $results[] = [
|
|
|
+ 'tenant' => $tenant->tenant_database,
|
|
|
+ 'status' => 'success',
|
|
|
+ 'message' => "Rolled back {$steps} step(s)"
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $results[] = [
|
|
|
+ 'tenant' => $tenant->tenant_database,
|
|
|
+ 'status' => 'error',
|
|
|
+ 'message' => $e->getMessage()
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'message' => 'Tenant rollback process completed',
|
|
|
+ 'results' => $results
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function setupMasterConnection()
|
|
|
+ {
|
|
|
+ config(['database.connections.master' => [
|
|
|
+ 'driver' => 'mysql',
|
|
|
+ 'host' => env('DB_HOST', '127.0.0.1'),
|
|
|
+ 'port' => env('DB_PORT', '3306'),
|
|
|
+ 'database' => $this->masterDatabase,
|
|
|
+ 'username' => env('DB_USERNAME'),
|
|
|
+ 'password' => env('DB_PASSWORD'),
|
|
|
+ 'charset' => 'utf8mb4',
|
|
|
+ 'collation' => 'utf8mb4_unicode_ci',
|
|
|
+ 'prefix' => '',
|
|
|
+ 'strict' => true,
|
|
|
+ 'engine' => null,
|
|
|
+ ]]);
|
|
|
+
|
|
|
+ config(['database.default' => 'master']);
|
|
|
+ DB::purge('master');
|
|
|
+ DB::reconnect('master');
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function setupTenantConnection($tenant)
|
|
|
+ {
|
|
|
+ config(['database.connections.tenant' => [
|
|
|
+ 'driver' => 'mysql',
|
|
|
+ 'host' => env('DB_HOST', '127.0.0.1'),
|
|
|
+ 'port' => env('DB_PORT', '3306'),
|
|
|
+ 'database' => $tenant->tenant_database,
|
|
|
+ 'username' => $tenant->tenant_username,
|
|
|
+ 'password' => $tenant->tenant_password,
|
|
|
+ 'charset' => 'utf8mb4',
|
|
|
+ 'collation' => 'utf8mb4_unicode_ci',
|
|
|
+ 'prefix' => '',
|
|
|
+ 'strict' => true,
|
|
|
+ 'engine' => null,
|
|
|
+ ]]);
|
|
|
+
|
|
|
+ config(['database.default' => 'tenant']);
|
|
|
+ DB::purge('tenant');
|
|
|
+ DB::reconnect('tenant');
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function getTenants()
|
|
|
+ {
|
|
|
+ return User::select('tenant_database', 'tenant_username', 'tenant_password')
|
|
|
+ ->whereNotNull('tenant_database')
|
|
|
+ ->distinct()
|
|
|
+ ->get();
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function getMasterMigrationFiles()
|
|
|
+ {
|
|
|
+ $migrationPath = database_path('migrations');
|
|
|
+ $files = File::glob($migrationPath . '/*.php');
|
|
|
+ $masterMigrations = [];
|
|
|
+
|
|
|
+ foreach ($files as $file) {
|
|
|
+ $content = File::get($file);
|
|
|
+ if (strpos($content, 'extends MasterMigration') !== false) {
|
|
|
+ $masterMigrations[] = basename($file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $masterMigrations;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function getTenantMigrationFiles()
|
|
|
+ {
|
|
|
+ $migrationPath = database_path('migrations');
|
|
|
+ $files = File::glob($migrationPath . '/*.php');
|
|
|
+ $tenantMigrations = [];
|
|
|
+
|
|
|
+ foreach ($files as $file) {
|
|
|
+ $content = File::get($file);
|
|
|
+ if (strpos($content, 'extends TenantMigration') !== false) {
|
|
|
+ $tenantMigrations[] = basename($file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $tenantMigrations;
|
|
|
+ }
|
|
|
+}
|