diff --git a/src/app/Console/Commands/MakeSchemaCommand.php b/src/app/Console/Commands/MakeSchemaCommand.php new file mode 100644 index 00000000..8ff69151 --- /dev/null +++ b/src/app/Console/Commands/MakeSchemaCommand.php @@ -0,0 +1,79 @@ +builder = DB::getSchemaBuilder(); + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + try { + $crew_id = $this->argument('team_id'); + $crew = Crew::find($crew_id); + + if(!$crew) { + $this->warn("Team " . $this->argument('team_id') . " doesn't exist yet."); + $this->info("Create the team before creating its schema"); + return Command::FAILURE; + } + + $migrationFolderPath = Config::get('database.migration_path'); + File::ensureDirectoryExists($migrationFolderPath . DIRECTORY_SEPARATOR . $crew_id); + + $dbName = Schema::getSchemaName($crew_id); + $schema = Schema::where('name', $dbName)->first(); + + if(!$schema) { + $schema['name'] = $dbName; + $schema['crew_id'] = $crew_id; + Schema::Create($schema); + $this->builder->createDatabase($dbName); + $this->info("Created database: " . $dbName); + } else { + $this->info("Database already exists for this team"); + } + + return Command::SUCCESS; + + } catch(\Exception $e) { + $this->error($e->getMessage()); + return Command::FAILURE; + } + } +} diff --git a/src/app/Console/Commands/MigrateCustomSchemaCommand.php b/src/app/Console/Commands/MigrateCustomSchemaCommand.php new file mode 100644 index 00000000..a9ee539f --- /dev/null +++ b/src/app/Console/Commands/MigrateCustomSchemaCommand.php @@ -0,0 +1,61 @@ +argument('team_id'); + $crew = Crew::find($crew_id); + + if(!$crew) { + $this->warn("Team " . $this->argument('team_id') . " doesn't exist yet."); + $this->info("Create the team before migrating to its schema"); + return Command::FAILURE; + } + + $dbName = Schema::getSchemaName($crew_id); + $schema = Schema::where("name", $dbName)->first(); + if(!$schema) { + $this->warn("No database found for " . $this->argument('team_id')); + $this->info("You can initiate a new schema with artisan make:schema {team_id}"); + return Command::FAILURE; + } + + $schema->openDatabaseConnection(); + $path = $this->option('path') ?? config('database.migration_path'); + + $migrationResult = $this->call('migrate', ['--database' => $dbName, '--path' => $path]); + + if ($migrationResult !== Command::SUCCESS) { + $this->warn("Something went wrong during the migration. Aborting."); + return Command::FAILURE; + } + return Command::SUCCESS; + } +} diff --git a/src/app/Models/Schema.php b/src/app/Models/Schema.php new file mode 100644 index 00000000..9dee0357 --- /dev/null +++ b/src/app/Models/Schema.php @@ -0,0 +1,55 @@ +belongsTo(Crew::class); + } + + public static function getSchemaName(string $crew_id): string + { + return 'db_scan_' . $crew_id; + } + + public function openDatabaseConnection(): void { + $dbName = $this->name; + $dbConfig = Config::get('database.connections.mysql'); + $dbConfig['database'] = $dbName; + Config::set('database.connections.' . $dbName, $dbConfig); + } + +} diff --git a/src/database/migrations/2025_01_15_143802_create_schemas_table.php b/src/database/migrations/2025_01_15_143802_create_schemas_table.php new file mode 100644 index 00000000..8bff73e5 --- /dev/null +++ b/src/database/migrations/2025_01_15_143802_create_schemas_table.php @@ -0,0 +1,34 @@ +uuid("id")->primary(); + $table->string("name")->unique(); + $table->foreignUuid("crew_id"); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('schemas'); + } +}; diff --git a/src/tests/Feature/CustomSchemaTest.php b/src/tests/Feature/CustomSchemaTest.php new file mode 100644 index 00000000..2c43d10f --- /dev/null +++ b/src/tests/Feature/CustomSchemaTest.php @@ -0,0 +1,96 @@ +create(); + $crew_id = $test_crew->id; + $db_name = Schema::getSchemaName($crew_id); + + $this->artisan('make:schema ' . $crew_id) + ->assertSuccessful() + ->expectsOutput("Created database: " . $db_name); + + $this->assertDatabaseHas('schemas', [ + 'crew_id' => $crew_id + ]); + + $migrationFolderPath = Config::get('database.migration_path'); + $this->assertTrue(File::exists($migrationFolderPath . DIRECTORY_SEPARATOR . $crew_id)); + + $dbConfig = Config::get('database.connections.mysql'); + $dbConfig['database'] = $db_name; + Config::set('database.connections.' . $db_name, $dbConfig); + + $this->assertEquals($db_name, DB::connection($db_name)->getDatabaseName()); + + } + + public function test_recreate_schema_for_existing_team() + { + $test_crew = Crew::factory()->create(); + $this->artisan('make:schema ' . $test_crew->id) + ->assertSuccessful(); + + $this->artisan('make:schema ' . $test_crew->id) + ->assertSuccessful() + ->expectsOutput('Database already exists for this team'); + } + + public function test_create_schema_for_non_existing_team() + { + $this->artisan('make:schema ' . 'test') + ->assertFailed() + ->expectsOutput("Team test doesn't exist yet."); + } + + public function test_migrate_to_non_existing_team() + { + $this->artisan('migrate:custom test') + ->assertFailed() + ->expectsOutput("Team test doesn't exist yet."); + } + + public function test_migrate_to_existing_team_without_schema() + { + $crew = Crew::factory()->create(); + + $this->artisan('migrate:custom ' . $crew->id) + ->assertFailed() + ->expectsOutput("No database found for " . $crew->id); + } + + public function test_migrate_to_existing_team_with_schema() + { + $crew = Crew::factory()->create(); + $db_name = Schema::getSchemaName($crew->id); + + $this->artisan('make:schema ' . $crew->id) + ->assertSuccessful(); + + $this->artisan('migrate:custom ' . $crew->id) + ->assertSuccessful(); + + $dbConfig = Config::get('database.connections.mysql'); + $dbConfig['database'] = $db_name; + Config::set('database.connections.' . $db_name, $dbConfig); + + $schemaBuilder = \Illuminate\Support\Facades\Schema::connection($db_name); + $this->assertTrue($schemaBuilder->hasTable("migrations")); + } +}