feat: create user command
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class CreateUser extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'user:create
|
||||||
|
{--name= : The unique user name}
|
||||||
|
{--email= : The unique email address}
|
||||||
|
{--role= : The user role: user, admin, or moderator}
|
||||||
|
{--locale=fr : The user locale}
|
||||||
|
{--verified : Mark the email address as verified}';
|
||||||
|
|
||||||
|
protected $description = 'Create an application user.';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$name = trim((string) ($this->option('name') ?: $this->ask('Name')));
|
||||||
|
$email = strtolower(trim((string) ($this->option('email') ?: $this->ask('Email address'))));
|
||||||
|
$role = (string) ($this->option('role') ?: $this->choice(
|
||||||
|
'Role',
|
||||||
|
array_column(UserRole::cases(), 'value'),
|
||||||
|
UserRole::USER->value,
|
||||||
|
));
|
||||||
|
$locale = strtolower(trim((string) $this->option('locale')));
|
||||||
|
|
||||||
|
$attributesValidator = Validator::make([
|
||||||
|
'name' => $name,
|
||||||
|
'email' => $email,
|
||||||
|
'role' => $role,
|
||||||
|
'locale' => $locale,
|
||||||
|
], [
|
||||||
|
'name' => ['required', 'string', 'min:3', 'max:32', 'regex:/^[a-zA-Z0-9_.-]+$/', 'unique:users,name'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
|
||||||
|
'role' => ['required', Rule::enum(UserRole::class)],
|
||||||
|
'locale' => ['required', Rule::in(config('app.supported_locales', ['fr', 'en']))],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($attributesValidator->fails()) {
|
||||||
|
foreach ($attributesValidator->errors()->all() as $error) {
|
||||||
|
$this->components->error($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$password = (string) $this->secret('Password');
|
||||||
|
$passwordConfirmation = (string) $this->secret('Confirm password');
|
||||||
|
|
||||||
|
$passwordValidator = Validator::make([
|
||||||
|
'password' => $password,
|
||||||
|
'password_confirmation' => $passwordConfirmation,
|
||||||
|
], [
|
||||||
|
'password' => [
|
||||||
|
'required',
|
||||||
|
'confirmed',
|
||||||
|
Password::min(8)
|
||||||
|
->mixedCase()
|
||||||
|
->letters()
|
||||||
|
->numbers()
|
||||||
|
->symbols(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($passwordValidator->fails()) {
|
||||||
|
foreach ($passwordValidator->errors()->all() as $error) {
|
||||||
|
$this->components->error($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trialDays = max(0, (int) config('billing.trial_days', 7));
|
||||||
|
|
||||||
|
$user = User::query()->create([
|
||||||
|
'name' => $name,
|
||||||
|
'email' => $email,
|
||||||
|
'email_verified_at' => $this->option('verified') ? now() : null,
|
||||||
|
'locale' => $locale,
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
'role' => UserRole::from($role),
|
||||||
|
'trial_ends_at' => $trialDays > 0 ? now()->addDays($trialDays) : null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->components->info("User {$user->email} created with ID {$user->getKey()} and role {$user->role->value}.");
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('creates a verified administrator with the application command', function () {
|
||||||
|
$this->artisan('user:create', [
|
||||||
|
'--name' => 'admin.bowli',
|
||||||
|
'--email' => 'ADMIN@EXAMPLE.COM',
|
||||||
|
'--role' => UserRole::ADMIN->value,
|
||||||
|
'--locale' => 'fr',
|
||||||
|
'--verified' => true,
|
||||||
|
])
|
||||||
|
->expectsQuestion('Password', 'Strong-password1')
|
||||||
|
->expectsQuestion('Confirm password', 'Strong-password1')
|
||||||
|
->assertSuccessful();
|
||||||
|
|
||||||
|
$user = User::query()->where('email', 'admin@example.com')->firstOrFail();
|
||||||
|
|
||||||
|
expect($user->name)->toBe('admin.bowli')
|
||||||
|
->and($user->role)->toBe(UserRole::ADMIN)
|
||||||
|
->and($user->locale)->toBe('fr')
|
||||||
|
->and($user->email_verified_at)->not->toBeNull()
|
||||||
|
->and($user->trial_ends_at)->not->toBeNull()
|
||||||
|
->and(Hash::check('Strong-password1', $user->password))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects duplicate users', function () {
|
||||||
|
User::factory()->create([
|
||||||
|
'name' => 'existing.user',
|
||||||
|
'email' => 'existing@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->artisan('user:create', [
|
||||||
|
'--name' => 'existing.user',
|
||||||
|
'--email' => 'existing@example.com',
|
||||||
|
'--role' => UserRole::USER->value,
|
||||||
|
])->assertFailed();
|
||||||
|
|
||||||
|
expect(User::query()->count())->toBe(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user