From b4966b0b22b671455f7c91a9f6d59a63f18f27a4 Mon Sep 17 00:00:00 2001 From: Derek Bourgeois Date: Fri, 10 Apr 2026 06:28:02 -0400 Subject: [PATCH] feat(admin): add first-admin bootstrap command --- app/Console/Commands/BootstrapFirstAdmin.php | 84 +++++++++++++++++++ .../Admin/BootstrapFirstAdminCommandTest.php | 31 +++++++ 2 files changed, 115 insertions(+) create mode 100644 app/Console/Commands/BootstrapFirstAdmin.php create mode 100644 tests/Feature/Admin/BootstrapFirstAdminCommandTest.php 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); +});