Bläddra i källkod

Gestione tenant

Luca Parisio 5 månader sedan
förälder
incheckning
ffe9ebacf1

+ 17 - 0
app/Database/Migrations/MasterMigration.php

@@ -0,0 +1,17 @@
+<?php
+namespace App\Database\Migrations;
+
+use Illuminate\Database\Migrations\Migration as BaseMigration;
+
+abstract class MasterMigration extends BaseMigration
+{
+    public function getConnection()
+    {
+        return 'master';
+    }
+
+    public function isMasterMigration(): bool
+    {
+        return true;
+    }
+}

+ 17 - 0
app/Database/Migrations/TenantMigration.php

@@ -0,0 +1,17 @@
+<?php
+namespace App\Database\Migrations;
+
+use Illuminate\Database\Migrations\Migration as BaseMigration;
+
+abstract class TenantMigration extends BaseMigration
+{
+    public function getConnection()
+    {
+        return 'tenant';
+    }
+
+    public function isTenantMigration(): bool
+    {
+        return true;
+    }
+}

+ 60 - 0
app/Http/Middleware/TenantMiddleware.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class TenantMiddleware
+{
+    /**
+     * Handle an incoming request.
+     */
+    public function handle(Request $request, Closure $next): Response
+    {
+        if (! $request->user()) {
+            return redirect('/');
+        }
+
+        $this->setupTenantConnection($request->user());
+
+        return $next($request);
+    }
+
+    /**
+     * Set up the tenant database connection
+     */
+    public function setupTenantConnection($user = null)
+    {
+        $user = $user ?: auth()->user();
+
+        if ($user) {
+            Log::info('Setting database connection', [
+                'database' => $user->tenant_database,
+                'username' => $user->tenant_username,
+                'password' => $user->tenant_password
+            ]);
+
+            config(['database.connections.tenant' => [
+                'driver' => 'mysql',
+                'host' => '127.0.0.1',
+                'port' => '3306',
+                'database' => $user->tenant_database,
+                'username' => $user->tenant_username,
+                'password' => $user->tenant_password,
+            ]]);
+
+            config(['database.default' => 'tenant']);
+            DB::purge('tenant');
+            DB::reconnect('tenant');
+
+            session(['currentClient' => $user->tenant_database]);
+
+            Log::info('Current database after setup: ' . DB::connection()->getDatabaseName());
+            Log::info('Current default connection: ' . DB::getDefaultConnection());
+        }
+    }
+}

+ 24 - 0
app/Providers/MigrationServiceProvider.php

@@ -0,0 +1,24 @@
+<?php
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use App\Console\Commands\MigrateMaster;
+use App\Console\Commands\MigrateTenants;
+
+class MigrationServiceProvider extends ServiceProvider
+{
+    public function register()
+    {
+        $this->app->singleton(\App\Services\MigrationService::class);
+    }
+
+    public function boot()
+    {
+        if ($this->app->runningInConsole()) {
+            $this->commands([
+                MigrateMaster::class,
+                MigrateTenants::class,
+            ]);
+        }
+    }
+}

+ 253 - 0
app/Services/MigrationService.php

@@ -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;
+    }
+}

+ 72 - 0
app/Services/MultiTenantAuthService.php

@@ -0,0 +1,72 @@
+<?php
+namespace App\Services;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Hash;
+class MultiTenantAuthService
+{
+    /**
+     * Authenticate user against both master and tenant databases
+     */
+    public static function authenticate($email, $password)
+    {
+        try {
+            $masterUser = DB::connection('mysql')->table('users')
+                ->where('email', $email)
+                ->first();
+
+            if (!$masterUser) {
+                Log::info('User not found in master database', ['email' => $email]);
+                return false;
+            }
+
+            if (!Hash::check($password, $masterUser->password)) {
+                Log::info('Password mismatch in master database', ['email' => $email]);
+                return false;
+            }
+
+            config(['database.connections.temp_tenant' => [
+                'driver' => 'mysql',
+                'host' => '127.0.0.1',
+                'port' => '3306',
+                'database' => $masterUser->tenant_database,
+                'username' => $masterUser->tenant_username,
+                'password' => $masterUser->tenant_password,
+            ]]);
+
+            $tenantUser = DB::connection('temp_tenant')->table('users')
+                ->where('email', $email)
+                ->first();
+
+            if (!$tenantUser) {
+                Log::info('User not found in tenant database', [
+                    'email' => $email,
+                    'tenant_db' => $masterUser->tenant_database
+                ]);
+                return false;
+            }
+
+            if (!Hash::check($password, $tenantUser->password)) {
+                Log::info('Password mismatch in tenant database', [
+                    'email' => $email,
+                    'tenant_db' => $masterUser->tenant_database
+                ]);
+                return false;
+            }
+
+            Log::info('Authentication successful for both databases', [
+                'email' => $email,
+                'tenant_db' => $masterUser->tenant_database
+            ]);
+
+            return $masterUser;
+
+        } catch (\Exception $e) {
+            Log::error('Authentication error', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+}

+ 1 - 1
database/migrations/0001_01_01_000000_create_users_table.php

@@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
-return new class extends Migration
+return new class extends MasterMigration
 {
     /**
      * Run the migrations.

+ 1 - 1
database/migrations/0001_01_01_000001_create_cache_table.php

@@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
-return new class extends Migration
+return new class extends MasterMigration
 {
     /**
      * Run the migrations.

+ 1 - 1
database/migrations/0001_01_01_000002_create_jobs_table.php

@@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
-return new class extends Migration
+return new class extends MasterMigration
 {
     /**
      * Run the migrations.

+ 52 - 0
database/migrations/2025_08_04_100000_add_fields_to_users_table.php

@@ -0,0 +1,52 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends MasterMigration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->integer('level')->default(0);
+            $table->integer('enabled')->default(1);
+            $table->string('cellulare')->nullable();
+            $table->string('telefono')->nullable();
+            $table->string('cognome')->nullable();
+            $table->boolean('first_login_completed')->default(false);
+            $table->timestamp('first_login_at')->nullable();
+            $table->string('tenant_host')->nullable();
+            $table->string('tenant_database')->nullable();
+            $table->string('tenant_username')->nullable();
+            $table->string('tenant_password')->nullable();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('level');
+            $table->dropColumn('enabled');
+            $table->dropColumn('cellulare');
+            $table->dropColumn('telefono');
+            $table->dropColumn('cognome');
+            $table->dropColumn('first_login_completed');
+            $table->dropColumn('first_login_at');
+            $table->dropColumn('tenant_host');
+            $table->dropColumn('tenant_database');
+            $table->dropColumn('tenant_username');
+            $table->dropColumn('tenant_password');
+        });
+    }
+};