diff --git a/app/Console/Commands/BootstrapFirstAdmin.php b/app/Console/Commands/BootstrapFirstAdmin.php new file mode 100644 index 0000000..16392a6 --- /dev/null +++ b/app/Console/Commands/BootstrapFirstAdmin.php @@ -0,0 +1,84 @@ +argument('email'); + + if (is_string($email) && $email !== '') { + return $this->promoteExistingUser($email); + } + + return $this->createFirstAdmin(); + } + + protected function createFirstAdmin(): int + { + $user = User::query()->first(); + + if ($user === null) { + $this->components->error('No users exist yet. Create a user first, then promote it with the email argument.'); + + return self::FAILURE; + } + + $this->ensureAdminRoleExists(); + + if ($user->isAn('admin')) { + $this->components->info("The first user, {$user->email}, is already an admin."); + + return self::SUCCESS; + } + + $user->assign('admin'); + Bouncer::refreshFor($user); + + $this->components->info("Promoted {$user->email} to admin."); + + return self::SUCCESS; + } + + protected function promoteExistingUser(string $email): int + { + $user = User::query()->where('email', $email)->first(); + + if ($user === null) { + $this->components->error("No user was found for {$email}."); + + return self::FAILURE; + } + + $this->ensureAdminRoleExists(); + + if ($user->isAn('admin')) { + $this->components->info("{$user->email} is already an admin."); + + return self::SUCCESS; + } + + $user->assign('admin'); + Bouncer::refreshFor($user); + + $this->components->info("Promoted {$user->email} to admin."); + + return self::SUCCESS; + } + + protected function ensureAdminRoleExists(): void + { + Bouncer::role()->firstOrCreate([ + 'name' => 'admin', + ]); + } +} diff --git a/tests/Feature/Admin/BootstrapFirstAdminCommandTest.php b/tests/Feature/Admin/BootstrapFirstAdminCommandTest.php new file mode 100644 index 0000000..e29949c --- /dev/null +++ b/tests/Feature/Admin/BootstrapFirstAdminCommandTest.php @@ -0,0 +1,31 @@ +create(); + + $this->artisan('app:bootstrap-first-admin') + ->assertExitCode(0); + + expect($user->fresh()->isAn('admin'))->toBeTrue(); +}); + +it('promotes an existing user by email', function (): void { + $user = User::factory()->create(); + + $this->artisan('app:bootstrap-first-admin', [ + 'email' => $user->email, + ])->assertExitCode(0); + + expect($user->fresh()->isAn('admin'))->toBeTrue(); +}); + +it('fails when the user does not exist', function (): void { + $this->artisan('app:bootstrap-first-admin', [ + 'email' => 'missing@example.com', + ])->assertExitCode(1); +});