Added the ability to manage a group of users.
This commit is contained in:
@@ -41,7 +41,7 @@ final class CreateUserAdmin extends Command
|
||||
$data = $validator->valid();
|
||||
|
||||
try {
|
||||
$role = $roleRepository->getRoleBySlug(SystemRole::Admin->value);
|
||||
$role = $roleRepository->getRoleByCode(SystemRole::Admin->value);
|
||||
if (is_null($role)) {
|
||||
$this->errorMessageAndStop('Administrator role not found.');
|
||||
}
|
||||
|
18
app/Contracts/Search.php
Normal file
18
app/Contracts/Search.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Pagination\CursorPaginator;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface Search
|
||||
{
|
||||
public function __construct(Relation | Builder $query);
|
||||
public function all(): Collection;
|
||||
public function get(int $limit): Collection;
|
||||
public function pagination(int $limit, int $page = 1): LengthAwarePaginator;
|
||||
public function cursorPaginate(int $limit): CursorPaginator;
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
@@ -8,5 +7,6 @@ interface ServiceResultError
|
||||
public function getCode(): ?int;
|
||||
public function getMessage(): string;
|
||||
public function getErrors(): array;
|
||||
public function getErrorsOrMessage(): array|string;
|
||||
public function getData(): array;
|
||||
}
|
||||
|
10
app/Dto/Builder/Role.php
Normal file
10
app/Dto/Builder/Role.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class Role
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
27
app/Dto/QuerySettingsDto.php
Normal file
27
app/Dto/QuerySettingsDto.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
final readonly class QuerySettingsDto
|
||||
{
|
||||
public function __construct(
|
||||
private int $limit,
|
||||
private int $page = 1,
|
||||
private array $queryWith = []
|
||||
) { }
|
||||
|
||||
public function getLimit(): int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getQueryWith(): array
|
||||
{
|
||||
return $this->queryWith;
|
||||
}
|
||||
}
|
24
app/Dto/Request/Private/Role/Index.php
Normal file
24
app/Dto/Request/Private/Role/Index.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\Role;
|
||||
|
||||
use App\Dto\Builder\Role;
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class Index extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private Role $roleBuilderDto,
|
||||
private int $page
|
||||
) { }
|
||||
|
||||
public function getRoleBuilderDto(): Role
|
||||
{
|
||||
return $this->roleBuilderDto;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
29
app/Dto/Request/Private/Role/StoreUpdate.php
Normal file
29
app/Dto/Request/Private/Role/StoreUpdate.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\Role;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private ?string $code,
|
||||
private array $permissions,
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
}
|
52
app/Enums/Permission.php
Normal file
52
app/Enums/Permission.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum Permission: string
|
||||
{
|
||||
case Role = 'role';
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->getBasePermissions();
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('permissions.' . $this->name);
|
||||
}
|
||||
|
||||
public function formatValue(string $permission): string
|
||||
{
|
||||
return $this->value . '.' . $permission;
|
||||
}
|
||||
|
||||
public static function toArrayList(): array
|
||||
{
|
||||
$permissions = [];
|
||||
foreach (self::cases() as $permissionEnum) {
|
||||
foreach ($permissionEnum->getPermissions() as $permissionName => $permissionTitle) {
|
||||
$name = $permissionEnum->formatValue($permissionName);
|
||||
$title = $permissionEnum->getTitle() . ' - ' . $permissionTitle;
|
||||
$permissions[$name] = $title;
|
||||
}
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
public static function toArrayListCodes(): array
|
||||
{
|
||||
return \array_keys(self::toArrayList());
|
||||
}
|
||||
|
||||
private function getBasePermissions(): array
|
||||
{
|
||||
return [
|
||||
'view' => __('permissions.Allowed to watch'),
|
||||
'create' => __('permissions.Allowed to create'),
|
||||
'update' => __('permissions.Allowed to edit'),
|
||||
'delete' => __('permissions.Allowed to delete'),
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Rule;
|
||||
|
||||
final class RoleSyncPermissionsCommandHandlerException extends \Exception
|
||||
{
|
||||
|
||||
}
|
92
app/Http/Controllers/Private/RolesController.php
Normal file
92
app/Http/Controllers/Private/RolesController.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Private\Roles\IndexRequest;
|
||||
use App\Http\Requests\Private\Roles\StoreUpdateRequest;
|
||||
use App\Services\Private\RoleService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class RolesController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RoleService $roleService
|
||||
) { }
|
||||
public function index(IndexRequest $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$data = $request->getDto();
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
);
|
||||
|
||||
$result = $this->roleService->index($data->getRoleBuilderDto(), $querySettingsDto, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/roles/index', $result->getData());
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->create($user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/roles/create', $result->getData());
|
||||
}
|
||||
|
||||
public function edit(int $id, Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->edit($id, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/roles/edit', $result->getData());
|
||||
}
|
||||
|
||||
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->store($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('roles.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->update($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('roles.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function destroy(int $id, Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->destroy($id, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('roles.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
30
app/Http/Requests/Private/Roles/IndexRequest.php
Normal file
30
app/Http/Requests/Private/Roles/IndexRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\Roles;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Builder\Role;
|
||||
use App\Dto\Request\Private\Role\Index;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$this->redirect = route('roles.index');
|
||||
return [
|
||||
'page' => ['nullable', 'numeric', 'min:1']
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Index
|
||||
{
|
||||
return new Index(
|
||||
roleBuilderDto: new Role(),
|
||||
page: (int) $this->input('page', 1)
|
||||
);
|
||||
}
|
||||
}
|
37
app/Http/Requests/Private/Roles/StoreUpdateRequest.php
Normal file
37
app/Http/Requests/Private/Roles/StoreUpdateRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\Roles;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Private\Role\StoreUpdate;
|
||||
use App\Rules\Permission;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'name' => ['required', 'max:255'],
|
||||
'permissions' => ['array', new Permission()]
|
||||
];
|
||||
if ($this->getMethod() === 'POST') {
|
||||
$rules['code'] = ['required', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/i'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
public function getDto(): StoreUpdate
|
||||
{
|
||||
return new StoreUpdate(
|
||||
name: $this->input('name'),
|
||||
code: $this->input('code', null),
|
||||
permissions: $this->input('permissions', [])
|
||||
);
|
||||
}
|
||||
}
|
21
app/Models/RolePermission.php
Normal file
21
app/Models/RolePermission.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Permission;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class RolePermission extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'role_permission';
|
||||
|
||||
public function getPermissionTitleAttribute(): string
|
||||
{
|
||||
$pemissions = Permission::toArrayList();
|
||||
|
||||
return $pemissions[$this->permission] ?? '';
|
||||
}
|
||||
}
|
24
app/Policies/Policy.php
Normal file
24
app/Policies/Policy.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
abstract readonly class Policy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return bool|null
|
||||
*/
|
||||
final public function before(User $user): ?bool
|
||||
{
|
||||
if ($user->is_admin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
34
app/Policies/RolePolicy.php
Normal file
34
app/Policies/RolePolicy.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
|
||||
final readonly class RolePolicy extends Policy
|
||||
{
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('role.view');
|
||||
}
|
||||
|
||||
public function view(User $user, Role $role): bool
|
||||
{
|
||||
return $user->hasPermission('role.view');
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('role.create');
|
||||
}
|
||||
|
||||
public function update(User $user, Role $role): bool
|
||||
{
|
||||
return $user->hasPermission('role.update');
|
||||
}
|
||||
|
||||
public function delete(User $user, Role $role): bool
|
||||
{
|
||||
return $user->hasPermission('Role.delete');
|
||||
}
|
||||
}
|
@@ -11,7 +11,10 @@ use App\Captcha\Images\Head;
|
||||
use App\Captcha\Images\ImageManager;
|
||||
use App\Captcha\Images\Lines;
|
||||
use App\Services\Api\V1\CaptchaService;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Services\Search\Search;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
@@ -29,6 +32,10 @@ final class AppServiceProvider extends ServiceProvider
|
||||
$this->app->bind(ImageBody::class, Body::class);
|
||||
$this->app->bind(ImageLines::class, Lines::class);
|
||||
|
||||
$this->app->bind(CreateSearchInstanceCommand::class, function () {
|
||||
return new CreateSearchInstanceCommand(Search::class);
|
||||
});
|
||||
|
||||
$this->app->bind(CaptchaService::class, function (Application $app) {
|
||||
return new CaptchaService(
|
||||
config: config('captcha', []),
|
||||
@@ -54,5 +61,7 @@ final class AppServiceProvider extends ServiceProvider
|
||||
->uncompromised()
|
||||
: $rule;
|
||||
});
|
||||
|
||||
Paginator::useBootstrapFive();
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
* @var array<class-string, class-string>
|
||||
*/
|
||||
protected $policies = [
|
||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||
\App\Models\Role::class => \App\Policies\RolePolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -2,12 +2,48 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Contracts\Search;
|
||||
use App\Models\Role;
|
||||
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||
use App\Services\Role\BuilderCommand;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
final readonly class RoleRepository
|
||||
{
|
||||
public function getRoleBySlug(string $slug): ?Role
|
||||
final public function __construct(
|
||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||
private BuilderCommand $builderCommand
|
||||
) { }
|
||||
|
||||
public function getRoleById(int $id): ?Role
|
||||
{
|
||||
return Role::query()->where('slug', $slug)->first();
|
||||
return Role::query()->where('id', $id)->first();
|
||||
}
|
||||
|
||||
public function getRoleByCode(string $code): ?Role
|
||||
{
|
||||
return Role::query()->where('code', $code)->first();
|
||||
}
|
||||
|
||||
public function getRoles(RoleBuilderDto $roleBuilderDto, array $with = []): Search
|
||||
{
|
||||
$query = $this->builderCommand->execute(
|
||||
query: Role::query()->with($with),
|
||||
roleBuilderDto: $roleBuilderDto
|
||||
);
|
||||
|
||||
return $this->createSearchInstanceCommand->execute($query);
|
||||
}
|
||||
|
||||
public function isExistsCode(string $code, ?int $exceptId = null): bool
|
||||
{
|
||||
return Role::query()
|
||||
->where('code', $code)
|
||||
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||
$query->where('id', '!=', $exceptId);
|
||||
})
|
||||
->withTrashed()
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
|
55
app/Rules/Permission.php
Normal file
55
app/Rules/Permission.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
final readonly class Permission implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (!is_string($value) && !is_array($value)) {
|
||||
$fail('validation.no_type')->translate(['type' => 'string, array']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$this->validatePermission($value, $fail);
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
$this->validatePermission($item, $fail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validatePermission(string $value, Closure $fail): void
|
||||
{
|
||||
$value = explode('.', $value, 2);
|
||||
|
||||
if (count($value) !== 2) {
|
||||
$fail('validation.enum')->translate();
|
||||
return;
|
||||
}
|
||||
|
||||
list($permissionEnum, $permission) = $value;
|
||||
$permissionEnum = \App\Enums\Permission::tryFrom($permissionEnum);
|
||||
if (is_null($permissionEnum)) {
|
||||
$fail('validation.enum')->translate();
|
||||
return;
|
||||
}
|
||||
|
||||
$permissions = $permissionEnum->getPermissions();
|
||||
if (!isset($permissions[$permission])) {
|
||||
$fail('validation.enum')->translate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@@ -30,6 +30,15 @@ final class ServiceResultError extends ServiceResult implements ServiceResultErr
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function getErrorsOrMessage(): array|string
|
||||
{
|
||||
if (!empty($this->getErrors())) {
|
||||
return $this->getErrors();
|
||||
}
|
||||
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return [
|
||||
|
23
app/ServiceResults/StoreUpdateResult.php
Normal file
23
app/ServiceResults/StoreUpdateResult.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class StoreUpdateResult extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Model $model,
|
||||
private readonly string $message
|
||||
) { }
|
||||
|
||||
public function getModel(): Model
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
165
app/Services/Private/RoleService.php
Normal file
165
app/Services/Private/RoleService.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Private;
|
||||
|
||||
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Dto\Request\Private\Role\StoreUpdate;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Repositories\RoleRepository;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use App\Services\Role\RoleCommandHandler;
|
||||
use App\Services\Role\RoleSyncPermissionsCommandHandler;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class RoleService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RoleRepository $roleRepository,
|
||||
private readonly RoleCommandHandler $roleCommandHandler,
|
||||
private readonly RoleSyncPermissionsCommandHandler $roleSyncPermissionsCommandHandler
|
||||
) { }
|
||||
|
||||
public function index(RoleBuilderDto $roleBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
if ($user->cannot('viewAny', Role::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
$roles = $this->roleRepository->getRoles(
|
||||
$roleBuilderDto,
|
||||
$querySettingsDto->getQueryWith()
|
||||
)->pagination(
|
||||
$querySettingsDto->getLimit(),
|
||||
$querySettingsDto->getPage()
|
||||
);
|
||||
|
||||
return $this->result([
|
||||
'roles' => $roles
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
if ($user->cannot('create', Role::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
return $this->result([
|
||||
'role' => new Role(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
$role = $this->roleRepository->getRoleById($id);
|
||||
|
||||
if (is_null($role)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('view', $role)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
return $this->result([
|
||||
'role' => $role,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
if ($user->cannot('create', Role::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
if ($this->roleRepository->isExistsCode($data->getCode())) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.code')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.code')])]
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$role = DB::transaction(function () use ($data) {
|
||||
$dataRole = $this->getDataRole($data);
|
||||
$dataRole['code'] = $data->getCode();
|
||||
|
||||
$role = $this->roleCommandHandler->handleStore($dataRole);
|
||||
$role = $this->roleSyncPermissionsCommandHandler->handle($role, $data->getPermissions());
|
||||
|
||||
return $role;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($role, __('The group was successfully created'));
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
$role = $this->roleRepository->getRoleById($id);
|
||||
|
||||
if (is_null($role)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('update', $role)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
try {
|
||||
$role = DB::transaction(function () use ($data, $role) {
|
||||
$dataRole = $this->getDataRole($data);
|
||||
|
||||
$role = $this->roleCommandHandler->handleUpdate($role, $dataRole);
|
||||
$role = $this->roleSyncPermissionsCommandHandler->handle($role, $data->getPermissions());
|
||||
|
||||
return $role;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($role, __('The group was successfully updated'));
|
||||
}
|
||||
|
||||
public function destroy(int $id, User $user): ServiceResultError|ServiceResultSuccess
|
||||
{
|
||||
$role = $this->roleRepository->getRoleById($id);
|
||||
|
||||
if (is_null($role)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('delete', $role)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($role) {
|
||||
$this->roleCommandHandler->handleDestroy($role);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->ok(__('The group has been deleted'));
|
||||
}
|
||||
|
||||
private function getDataRole(StoreUpdate $data): array
|
||||
{
|
||||
return [
|
||||
'name' => $data->getName(),
|
||||
];
|
||||
}
|
||||
}
|
15
app/Services/Role/BuilderCommand.php
Normal file
15
app/Services/Role/BuilderCommand.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Role;
|
||||
|
||||
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class BuilderCommand
|
||||
{
|
||||
public function execute(Relation | Builder $query, RoleBuilderDto $roleBuilderDto): Relation | Builder
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
}
|
26
app/Services/Role/RoleCommandHandler.php
Normal file
26
app/Services/Role/RoleCommandHandler.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Role;
|
||||
|
||||
use App\Models\Role;
|
||||
|
||||
final readonly class RoleCommandHandler
|
||||
{
|
||||
public function handleStore(array $data): Role
|
||||
{
|
||||
return Role::create($data);
|
||||
}
|
||||
|
||||
public function handleUpdate(Role $role, array $data): Role
|
||||
{
|
||||
$role->update($data);
|
||||
$role->touch();
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
public function handleDestroy(Role $role): void
|
||||
{
|
||||
$role->delete();
|
||||
}
|
||||
}
|
57
app/Services/Role/RoleSyncPermissionsCommandHandler.php
Normal file
57
app/Services/Role/RoleSyncPermissionsCommandHandler.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Role;
|
||||
|
||||
use App\Enums\Permission;
|
||||
use App\Exceptions\Rule\RoleSyncPermissionsCommandHandlerException;
|
||||
use App\Models\Role;
|
||||
use App\Models\RolePermission;
|
||||
|
||||
final readonly class RoleSyncPermissionsCommandHandler
|
||||
{
|
||||
public function handle(Role $role, array $dataPermissions): Role
|
||||
{
|
||||
$rolePermissions = $role->permissions->pluck('id', 'permission')->toArray();
|
||||
|
||||
$data = $this->getInsertDeleteData($role->id, $rolePermissions, $dataPermissions);
|
||||
|
||||
if (!empty($data['insert'])) {
|
||||
RolePermission::query()->insert($data['insert']);
|
||||
}
|
||||
|
||||
if (!empty($data['delete'])) {
|
||||
RolePermission::query()->whereIn('id', $data['delete'])->delete();
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
private function getInsertDeleteData(int $roleId, array $rolePermissions, array $permissionsData): array
|
||||
{
|
||||
$data = [
|
||||
'insert' => [],
|
||||
'delete' => []
|
||||
];
|
||||
|
||||
$permissions = Permission::toArrayListCodes();
|
||||
foreach ($permissionsData as $permission) {
|
||||
if (array_search($permission, $permissions) === false) {
|
||||
throw new RoleSyncPermissionsCommandHandlerException('Таких разрешений в системе нет: ' . $permission);
|
||||
}
|
||||
|
||||
if (isset($rolePermissions[$permission])) {
|
||||
unset($rolePermissions[$permission]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['insert'][] = [
|
||||
'role_id' => $roleId,
|
||||
'permission' => $permission,
|
||||
];
|
||||
}
|
||||
|
||||
$data['delete'] = array_values($rolePermissions);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
19
app/Services/Search/CreateSearchInstanceCommand.php
Normal file
19
app/Services/Search/CreateSearchInstanceCommand.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Search;
|
||||
|
||||
use App\Contracts\Search;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
final readonly class CreateSearchInstanceCommand
|
||||
{
|
||||
public function __construct(
|
||||
private string $abstract
|
||||
) { }
|
||||
|
||||
public function execute(Relation | Builder $query): Search
|
||||
{
|
||||
return new $this->abstract($query);
|
||||
}
|
||||
}
|
80
app/Services/Search/Search.php
Normal file
80
app/Services/Search/Search.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Search;
|
||||
|
||||
use App\Models\Role;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Pagination\CursorPaginator;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Contracts\Search as SearchContract;
|
||||
|
||||
final readonly class Search implements SearchContract
|
||||
{
|
||||
public function __construct(
|
||||
private Relation|Builder $query
|
||||
) { }
|
||||
|
||||
public function all(): Collection
|
||||
{
|
||||
return $this->query->get();
|
||||
}
|
||||
|
||||
public function get(int $limit): Collection
|
||||
{
|
||||
return $this->query->limit($limit)->get();
|
||||
}
|
||||
|
||||
public function pagination(int $limit, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
if ($page > 100) {
|
||||
return $this->paginationPerfomance($limit, $page);
|
||||
}
|
||||
return $this->query->paginate($limit, page: $page)->withQueryString();
|
||||
}
|
||||
|
||||
public function cursorPaginate(int $limit): CursorPaginator
|
||||
{
|
||||
return $this->query->cursorPaginate($limit);
|
||||
}
|
||||
|
||||
private function paginationPerfomance(int $limit, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
$total = $this->query->clone()->count();
|
||||
$options = [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => 'page',
|
||||
];
|
||||
|
||||
$result = collect();
|
||||
if ($total > 0) {
|
||||
$result = $this->subQuery($limit, $page);
|
||||
}
|
||||
|
||||
$pagination = Container::getInstance()->makeWith(LengthAwarePaginator::class, [
|
||||
'items' => $result,
|
||||
'total' => $total,
|
||||
'perPage' => $limit,
|
||||
'currentPage' => $page,
|
||||
'options' => $options
|
||||
]);
|
||||
|
||||
return $pagination->withQueryString();
|
||||
}
|
||||
|
||||
private function subQuery(int $limit, int $page): Collection
|
||||
{
|
||||
$table = $this->query->getModel()->getTable();
|
||||
return $this->query->getModel()::query()
|
||||
->select($table.'.*')
|
||||
->with($this->query->getEagerLoads())
|
||||
->from(
|
||||
clone $this->query->select('id')->forPage($page, $limit),
|
||||
'q'
|
||||
)->join($table.' as '.$table, $table.'.id', '=', 'q.id')
|
||||
->get();
|
||||
}
|
||||
}
|
@@ -6,6 +6,8 @@ namespace App\Services;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
abstract class Service
|
||||
@@ -45,6 +47,11 @@ abstract class Service
|
||||
return new ServiceResultSuccess($message);
|
||||
}
|
||||
|
||||
final protected function resultStoreUpdateModel(Model $model, string $message = 'OK'): StoreUpdateResult
|
||||
{
|
||||
return new StoreUpdateResult($model, $message);
|
||||
}
|
||||
|
||||
final protected function result(array $data = []): ServiceResultArray
|
||||
{
|
||||
return new ServiceResultArray(data: $data);
|
||||
|
62
app/View/Components/Private/Forms/PermissionsForRole.php
Normal file
62
app/View/Components/Private/Forms/PermissionsForRole.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Private\Forms;
|
||||
|
||||
use App\Enums\Permission;
|
||||
use App\Models\Role;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class PermissionsForRole extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly Role $role,
|
||||
private readonly array $value
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getValue(): Collection
|
||||
{
|
||||
$value = old($this->getRequestName(), $this->value);
|
||||
return collect($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Role
|
||||
*/
|
||||
private function getRole(): Role
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('private.components.forms.permissions_for_role', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'permissions' => Permission::cases(),
|
||||
'role' => $this->getRole(),
|
||||
'value' => $this->getValue(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user