From 1efba6e175ef26b0458462a6bd2870f15f6894ff Mon Sep 17 00:00:00 2001 From: vqlion Date: Thu, 16 Jan 2025 10:27:26 +0100 Subject: [PATCH 1/5] feat(schemas): schema creation command - add command to create a schema for a specific team - add schema model issue: #517 --- .../Console/Commands/MakeSchemaCommand.php | 77 +++++++++++++++++++ src/app/Models/Schema.php | 55 +++++++++++++ ...2025_01_15_143802_create_schemas_table.php | 34 ++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/app/Console/Commands/MakeSchemaCommand.php create mode 100644 src/app/Models/Schema.php create mode 100644 src/database/migrations/2025_01_15_143802_create_schemas_table.php diff --git a/src/app/Console/Commands/MakeSchemaCommand.php b/src/app/Console/Commands/MakeSchemaCommand.php new file mode 100644 index 00000000..ba9360a1 --- /dev/null +++ b/src/app/Console/Commands/MakeSchemaCommand.php @@ -0,0 +1,77 @@ +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; + } + + $dbName = Schema::getSchemaName($crew_id); + $schema = Schema::where('name', $dbName)->first(); + + if(!$schema) { + $this->info("Creating database " . $dbName); + $schema['name'] = $dbName; + $schema['crew_id'] = $crew_id; + Schema::Create($schema); + $this->builder->createDatabase($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/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'); + } +}; From 53b3a36857a1684904a0ade3902e608d870deb71 Mon Sep 17 00:00:00 2001 From: vqlion Date: Thu, 16 Jan 2025 10:28:18 +0100 Subject: [PATCH 2/5] feat(schemas): schema migration command - add command to migrate a specific schema from a team - can provide a custom migration path for teams specific migrations issue: #517 --- .../Commands/MigrateCustomSchemaCommand.php | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/app/Console/Commands/MigrateCustomSchemaCommand.php diff --git a/src/app/Console/Commands/MigrateCustomSchemaCommand.php b/src/app/Console/Commands/MigrateCustomSchemaCommand.php new file mode 100644 index 00000000..1200a4fc --- /dev/null +++ b/src/app/Console/Commands/MigrateCustomSchemaCommand.php @@ -0,0 +1,56 @@ +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'); + + $this->call('migrate', [ '--seed' => false, '--database' => $dbName, '--path' => $path]); + return Command::SUCCESS; + } +} From b9f20c76bd515af6626061a7d2f2fb629a5bafec Mon Sep 17 00:00:00 2001 From: vqlion Date: Fri, 17 Jan 2025 16:56:57 +0100 Subject: [PATCH 3/5] feat(schemas): create team-specific migration folder the create schema command also creates a migration folder for the team issue: #517 --- src/app/Console/Commands/MakeSchemaCommand.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/Console/Commands/MakeSchemaCommand.php b/src/app/Console/Commands/MakeSchemaCommand.php index ba9360a1..2b8dbe5c 100644 --- a/src/app/Console/Commands/MakeSchemaCommand.php +++ b/src/app/Console/Commands/MakeSchemaCommand.php @@ -6,10 +6,9 @@ use App\Models\Schema; use Illuminate\Console\Command; use Illuminate\Database\Schema\Builder; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; -use PDO; +use Illuminate\Support\Facades\File; class MakeSchemaCommand extends Command { @@ -25,7 +24,7 @@ class MakeSchemaCommand extends Command * * @var string */ - protected $description = 'Generates a schema for a team in the database if the schema doesn\'t already exist'; + protected $description = 'Generates a schema for a team in the database and creates the associated migration folder if the schema doesn\'t already exist'; protected Builder $builder; @@ -54,17 +53,20 @@ public function handle() 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) { - $this->info("Creating database " . $dbName); $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"); + $this->info("Database already exists for this team "); } return Command::SUCCESS; From 6c371b15cd93cd0c9623f52d8169a28ab6446bb5 Mon Sep 17 00:00:00 2001 From: vqlion Date: Tue, 21 Jan 2025 16:28:26 +0100 Subject: [PATCH 4/5] test(schemas): add tests - test creating a new custom schema for a team - test migrate to a custom schema issue: #517 --- .../Console/Commands/MakeSchemaCommand.php | 2 +- .../Commands/MigrateCustomSchemaCommand.php | 2 +- src/tests/Feature/CustomSchemaTest.php | 96 +++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/tests/Feature/CustomSchemaTest.php diff --git a/src/app/Console/Commands/MakeSchemaCommand.php b/src/app/Console/Commands/MakeSchemaCommand.php index 2b8dbe5c..8ff69151 100644 --- a/src/app/Console/Commands/MakeSchemaCommand.php +++ b/src/app/Console/Commands/MakeSchemaCommand.php @@ -66,7 +66,7 @@ public function handle() $this->builder->createDatabase($dbName); $this->info("Created database: " . $dbName); } else { - $this->info("Database already exists for this team "); + $this->info("Database already exists for this team"); } return Command::SUCCESS; diff --git a/src/app/Console/Commands/MigrateCustomSchemaCommand.php b/src/app/Console/Commands/MigrateCustomSchemaCommand.php index 1200a4fc..ddf4dd03 100644 --- a/src/app/Console/Commands/MigrateCustomSchemaCommand.php +++ b/src/app/Console/Commands/MigrateCustomSchemaCommand.php @@ -50,7 +50,7 @@ public function handle() $schema->openDatabaseConnection(); $path = $this->option('path') ?? config('database.migration_path'); - $this->call('migrate', [ '--seed' => false, '--database' => $dbName, '--path' => $path]); + $this->call('migrate', ['--database' => $dbName, '--path' => $path]); return Command::SUCCESS; } } 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")); + } +} From ace6224f138f94eb3253c283eb53c80534a89238 Mon Sep 17 00:00:00 2001 From: vqlion Date: Fri, 31 Jan 2025 11:25:42 +0100 Subject: [PATCH 5/5] feat(schemas): error handling add error handling to migrate command call issue: #517 --- src/app/Console/Commands/MigrateCustomSchemaCommand.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/Console/Commands/MigrateCustomSchemaCommand.php b/src/app/Console/Commands/MigrateCustomSchemaCommand.php index ddf4dd03..a9ee539f 100644 --- a/src/app/Console/Commands/MigrateCustomSchemaCommand.php +++ b/src/app/Console/Commands/MigrateCustomSchemaCommand.php @@ -50,7 +50,12 @@ public function handle() $schema->openDatabaseConnection(); $path = $this->option('path') ?? config('database.migration_path'); - $this->call('migrate', ['--database' => $dbName, '--path' => $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; } }