Version 0.7.0 #1

Merged
kor-elf merged 90 commits from develop into main 2023-12-08 21:18:23 +06:00
8 changed files with 341 additions and 0 deletions
Showing only changes of commit 6b2aff910b - Show all commits

View File

@ -0,0 +1,94 @@
<?php declare(strict_types=1);
namespace App\Console\Commands;
use App\Dto\User\ManyRoleDto;
use App\Enums\SystemRole;
use App\Repositories\RoleRepository;
use App\Services\User\UserCommandHandler;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\Validator;
final class CreateUserAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:create-user-admin {email} {password}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create admin user.';
/**
* Execute the console command.
*/
public function handle(UserCommandHandler $userCommandHandler, RoleRepository $roleRepository): void
{
$validator = $this->getData();
if ($validator->fails()) {
$this->errorMessageAndStop($validator->errors()->all());
}
$data = $validator->valid();
try {
$role = $roleRepository->getRoleBySlug(SystemRole::Admin->value);
if (is_null($role)) {
$this->errorMessageAndStop('Administrator role not found.');
}
$user = DB::transaction(function () use($data, $userCommandHandler, $role) {
$data['name'] = 'Administrator';
$user = $userCommandHandler->handleStore($data, $data['password']);
$userCommandHandler->handleConfirmationByEmail($user);
$roles = new ManyRoleDto([$role->id]);
$userCommandHandler->handleSyncRoles($user, $roles);
return $user;
});
} catch (\Throwable $e) {
$this->errorMessageAndStop($e->getMessage());
}
$this->info('The command was successful!');
}
private function getData(): Validator
{
return ValidatorFacade::make([
'email' => $this->argument('email'),
'password' => $this->argument('password'),
], [
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', Password::default()],
]);
}
private function stop(): never
{
exit;
}
private function errorMessageAndStop(string | array $error): never
{
$this->info('User not created. See error messages below:');
if (is_array($error)) {
foreach ($error as $err) {
$this->error($err);
}
} else {
$this->error($error);
}
$this->stop();
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace App\Dto\User;
use App\Exceptions\Dto\User\ManyRoleDtoException;
final class ManyRoleDto
{
private array $roles = [];
public function __construct(array $roles = []) {
foreach ($roles as $role) {
if (!is_numeric($role) || is_float($role)) {
throw new ManyRoleDtoException('Not an integer: ' . $role . '.');
}
$this->add((int) $role);
}
}
public function add(int $id): void
{
if ($id < 1) {
throw new ManyRoleDtoException('Only Integer > 0.');
}
$this->roles[] = $id;
}
public function toArray(): array
{
return $this->roles;
}
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace App\Exceptions\Dto\User;
final class ManyRoleDtoException extends \Exception
{
}

59
app/Models/Role.php Normal file
View File

@ -0,0 +1,59 @@
<?php declare(strict_types=1);
namespace App\Models;
use App\Models\Enums\SystemRoleEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\hasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
final class Role extends Model
{
use HasFactory, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'slug'
];
public function scopeLatest(Builder $query): Builder
{
return $query->orderBy('id', 'desc');
}
public function scopeAlphavit(Builder $query): Builder
{
return $query->orderBy('name', 'asc');
}
public function permissions(): hasMany
{
return $this->hasMany(RolePermission::class, 'role_id', 'id');
}
/**
* Проверяем можем мы удалять эту группу или нет.
* Есть системные группы, которые нельзя удалять.
*/
public function isRemove(): bool
{
$dontRemove = SystemRoleEnum::toArray();
return (array_search($this->slug, $dontRemove) === false);
}
/**
* Проверка эта группа самая главная или нет.
*/
public function isAdmin(): bool
{
return ($this->slug === SystemRoleEnum::Admin->value);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@ -13,6 +14,15 @@ final class User extends Authenticatable
{ {
use HasApiTokens, HasFactory, Notifiable, SoftDeletes; use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
/**
* The model's default values for attributes.
*
* @var array
*/
protected $attributes = [
'is_active' => true,
];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
@ -46,4 +56,12 @@ final class User extends Authenticatable
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'is_active' => 'boolean' 'is_active' => 'boolean'
]; ];
/**
* Return the user's roles
*/
public function roles(): belongsToMany
{
return $this->belongsToMany(Role::class);
}
} }

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace App\Repositories;
use App\Models\Role;
final readonly class RoleRepository
{
public function getRoleBySlug(string $slug): ?Role
{
return Role::query()->where('slug', $slug)->first();
}
}

View File

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace App\Services\User;
use App\Dto\User\ManyRoleDto;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
final readonly class UserCommandHandler
{
public function handleStore(array $data, int|string $password): User
{
$data['email'] = Str::lower($data['email']);
$data['password'] = $this->hashPassword($password);
$user = User::create($data);
return $user;
}
public function handleUpdate(User $user, array $data): User
{
if (isset($data['email'])) {
$data['email'] = Str::lower($data['email']);
}
if (isset($data['password'])) {
unset($data['password']);
}
$user->update($data);
$user->touch();
return $user;
}
public function handleConfirmationByEmail(User $user): void
{
$user->update(['email_verified_at' => new Carbon('NOW')]);
}
public function handleUpdatePassword(User $user, int|string $password): void
{
$user->update(['password' => $this->hashPassword($password)]);
}
public function handleSyncRoles(User $user, ManyRoleDto $roles): void
{
$user->roles()->sync($roles->toArray());
}
private function hashPassword(int|string $password): string
{
return Hash::make($password);
}
}

View File

@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Carbon;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
$table->softDeletes();
});
DB::table('roles')->insert([
'name' => 'Administrator',
'slug' => 'admin',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
Schema::create('role_user', function (Blueprint $table) {
$table->unsignedBigInteger('user_id')->index();
$table->unsignedBigInteger('role_id');
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('role_id')->references('id')->on('roles');
$table->primary(['user_id', 'role_id']);
});
Schema::create('role_permission', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('role_id')->index();
$table->string('permission');
$table->foreign('role_id')->references('id')->on('roles');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('role_permission');
Schema::dropIfExists('role_user');
Schema::dropIfExists('roles');
}
};