From 081bd6ef8c00c40ba9bc5505fa2064e8b1d211b1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 26 May 2026 17:05:54 +0200 Subject: [PATCH] fix(seeding): ensure root user joins root team Create the root team before production seeding depends on it, reuse the existing root team when creating root users, and cover the production seeder flow with a feature test. --- app/Actions/Fortify/CreateNewUser.php | 6 ++- app/Models/User.php | 10 ++++ database/seeders/ProductionSeeder.php | 10 ++++ database/seeders/RootUserSeeder.php | 7 +++ tests/Feature/ProductionSeederTest.php | 68 ++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/ProductionSeederTest.php diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 7ea6a871e..cddf66389 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -2,6 +2,7 @@ namespace App\Actions\Fortify; +use App\Models\Team; use App\Models\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; @@ -44,7 +45,10 @@ class CreateNewUser implements CreatesNewUsers 'password' => Hash::make($input['password']), ]); $user->save(); - $team = $user->teams()->first(); + $team = $user->teams()->first() ?? Team::find(0); + if ($team !== null && ! $user->teams()->where('team_id', $team->id)->exists()) { + $user->teams()->attach($team, ['role' => 'owner']); + } // Disable registration after first user is created $settings = instanceSettings(); diff --git a/app/Models/User.php b/app/Models/User.php index 237f3836f..990c34b0b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -98,8 +98,18 @@ class User extends Authenticatable implements SendsEmail $team['id'] = 0; $team['name'] = 'Root Team'; } + $new_team = $user->id === 0 ? Team::find(0) : null; + + if ($new_team !== null) { + $new_team->forceFill($team); + $new_team->save(); + + return; + } + $new_team = (new Team)->forceFill($team); $new_team->save(); + $user->teams()->attach($new_team, ['role' => 'owner']); }); diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index 511af1a9f..4d492a297 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -32,6 +32,16 @@ class ProductionSeeder extends Seeder echo " Running in self-hosted mode.\n"; } + if (Team::find(0) === null) { + (new Team)->forceFill([ + 'id' => 0, + 'name' => 'Root Team', + 'description' => 'The root team', + 'personal_team' => true, + 'show_boarding' => true, + ])->save(); + } + if (User::find(0) !== null && Team::find(0) !== null) { if (DB::table('team_user')->where('user_id', 0)->first() === null) { DB::table('team_user')->insert([ diff --git a/database/seeders/RootUserSeeder.php b/database/seeders/RootUserSeeder.php index c4e93af63..9bc93a9a9 100644 --- a/database/seeders/RootUserSeeder.php +++ b/database/seeders/RootUserSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use App\Models\InstanceSettings; +use App\Models\Team; use App\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; @@ -52,6 +53,12 @@ class RootUserSeeder extends Seeder 'password' => Hash::make(env('ROOT_USER_PASSWORD')), ]); $user->save(); + + $team = Team::find(0); + if ($team !== null && ! $user->teams()->where('team_id', 0)->exists()) { + $user->teams()->attach($team, ['role' => 'owner']); + } + echo "\n SUCCESS Root user created successfully.\n\n"; } catch (\Exception $e) { echo "\n ERROR Failed to create root user: {$e->getMessage()}\n\n"; diff --git a/tests/Feature/ProductionSeederTest.php b/tests/Feature/ProductionSeederTest.php new file mode 100644 index 000000000..03da9f952 --- /dev/null +++ b/tests/Feature/ProductionSeederTest.php @@ -0,0 +1,68 @@ + 'log', + 'constants.coolify.is_windows_docker_desktop' => true, + ]); + Queue::fake(); + StartProxy::shouldRun()->andReturn('OK'); + + Server::creating(function (Server $server) { + if ((int) $server->getKey() === 0) { + expect(Team::find(0))->not->toBeNull(); + } + }); + + Server::created(function (Server $server) { + SslCertificate::create([ + 'server_id' => $server->id, + 'common_name' => 'Coolify CA Certificate', + 'ssl_certificate' => 'certificate', + 'ssl_private_key' => 'private-key', + 'valid_until' => now()->addYear(), + 'is_ca_certificate' => true, + ]); + }); + + $this->seed(ProductionSeeder::class); + + $rootTeam = Team::find(0); + $localhostServer = Server::find(0); + + expect($rootTeam)->not->toBeNull() + ->and($localhostServer)->not->toBeNull() + ->and($localhostServer->team_id)->toBe(0); + + expect(SharedEnvironmentVariable::query() + ->where('type', 'server') + ->where('server_id', 0) + ->where('team_id', 0) + ->pluck('key') + ->all() + )->toContain('COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'); + + instanceSettings()->update(['is_registration_enabled' => true]); + + $rootUser = app(CreateNewUser::class)->create([ + 'name' => 'Root User', + 'email' => 'root@example.com', + 'password' => 'Password123!', + 'password_confirmation' => 'Password123!', + ]); + + expect(Team::whereKey(0)->count())->toBe(1) + ->and($rootUser->teams()->where('team_id', 0)->exists())->toBeTrue(); +});