Version 0.7.0 #1
@ -41,7 +41,7 @@ final class CreateUserAdmin extends Command
|
|||||||
$data = $validator->valid();
|
$data = $validator->valid();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$role = $roleRepository->getRoleBySlug(SystemRole::Admin->value);
|
$role = $roleRepository->getRoleByCode(SystemRole::Admin->value);
|
||||||
if (is_null($role)) {
|
if (is_null($role)) {
|
||||||
$this->errorMessageAndStop('Administrator role not found.');
|
$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
|
<?php declare(strict_types=1);
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Contracts;
|
namespace App\Contracts;
|
||||||
|
|
||||||
@ -8,5 +7,6 @@ interface ServiceResultError
|
|||||||
public function getCode(): ?int;
|
public function getCode(): ?int;
|
||||||
public function getMessage(): string;
|
public function getMessage(): string;
|
||||||
public function getErrors(): array;
|
public function getErrors(): array;
|
||||||
|
public function getErrorsOrMessage(): array|string;
|
||||||
public function getData(): array;
|
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\ImageManager;
|
||||||
use App\Captcha\Images\Lines;
|
use App\Captcha\Images\Lines;
|
||||||
use App\Services\Api\V1\CaptchaService;
|
use App\Services\Api\V1\CaptchaService;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use App\Services\Search\Search;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
|
use Illuminate\Pagination\Paginator;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Validation\Rules\Password;
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
@ -29,6 +32,10 @@ final class AppServiceProvider extends ServiceProvider
|
|||||||
$this->app->bind(ImageBody::class, Body::class);
|
$this->app->bind(ImageBody::class, Body::class);
|
||||||
$this->app->bind(ImageLines::class, Lines::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) {
|
$this->app->bind(CaptchaService::class, function (Application $app) {
|
||||||
return new CaptchaService(
|
return new CaptchaService(
|
||||||
config: config('captcha', []),
|
config: config('captcha', []),
|
||||||
@ -54,5 +61,7 @@ final class AppServiceProvider extends ServiceProvider
|
|||||||
->uncompromised()
|
->uncompromised()
|
||||||
: $rule;
|
: $rule;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Paginator::useBootstrapFive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
* @var array<class-string, class-string>
|
* @var array<class-string, class-string>
|
||||||
*/
|
*/
|
||||||
protected $policies = [
|
protected $policies = [
|
||||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
\App\Models\Role::class => \App\Policies\RolePolicy::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,12 +2,48 @@
|
|||||||
|
|
||||||
namespace App\Repositories;
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
use App\Models\Role;
|
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
|
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;
|
return $this->errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getErrorsOrMessage(): array|string
|
||||||
|
{
|
||||||
|
if (!empty($this->getErrors())) {
|
||||||
|
return $this->getErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
public function getData(): array
|
public function getData(): array
|
||||||
{
|
{
|
||||||
return [
|
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\ServiceResultArray;
|
||||||
use App\ServiceResults\ServiceResultError;
|
use App\ServiceResults\ServiceResultError;
|
||||||
use App\ServiceResults\ServiceResultSuccess;
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
abstract class Service
|
abstract class Service
|
||||||
@ -45,6 +47,11 @@ abstract class Service
|
|||||||
return new ServiceResultSuccess($message);
|
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
|
final protected function result(array $data = []): ServiceResultArray
|
||||||
{
|
{
|
||||||
return new ServiceResultArray(data: $data);
|
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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -47,5 +47,12 @@
|
|||||||
"Profile saved successfully": "Profile saved successfully",
|
"Profile saved successfully": "Profile saved successfully",
|
||||||
"The password has been changed": "The password has been changed",
|
"The password has been changed": "The password has been changed",
|
||||||
"Default": "default",
|
"Default": "default",
|
||||||
"The settings have been saved": "The settings have been saved"
|
"The settings have been saved": "The settings have been saved",
|
||||||
|
"Access is denied": "Access is denied!",
|
||||||
|
"Delete": "Delete",
|
||||||
|
"Do you want to delete?": "Do you want to delete?",
|
||||||
|
"Create": "Create",
|
||||||
|
"The group was successfully created": "The group was successfully created",
|
||||||
|
"The group was successfully updated": "The group was successfully updated",
|
||||||
|
"The group has been deleted": "The group has been deleted"
|
||||||
}
|
}
|
||||||
|
9
lang/en/permissions.php
Normal file
9
lang/en/permissions.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Role' => 'User group',
|
||||||
|
'Allowed to watch' => 'Allowed to watch',
|
||||||
|
'Allowed to create' => 'Allowed to create',
|
||||||
|
'Allowed to edit' => 'Allowed to edit',
|
||||||
|
'Allowed to delete' => 'Allowed to delete',
|
||||||
|
];
|
6
lang/en/sections.php
Normal file
6
lang/en/sections.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Dashboard' => 'Dashboard',
|
||||||
|
'User group' => 'User group',
|
||||||
|
];
|
@ -141,6 +141,7 @@ return [
|
|||||||
'uppercase' => 'The :attribute must be uppercase.',
|
'uppercase' => 'The :attribute must be uppercase.',
|
||||||
'url' => 'The :attribute format is invalid.',
|
'url' => 'The :attribute format is invalid.',
|
||||||
'uuid' => 'The :attribute must be a valid UUID.',
|
'uuid' => 'The :attribute must be a valid UUID.',
|
||||||
|
'no_type' => 'The :attribute can only use: :type.',
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'address' => 'address',
|
'address' => 'address',
|
||||||
'age' => 'age',
|
'age' => 'age',
|
||||||
@ -215,5 +216,7 @@ return [
|
|||||||
'year' => 'year',
|
'year' => 'year',
|
||||||
'lang' => 'language',
|
'lang' => 'language',
|
||||||
'timezone' => 'timezone',
|
'timezone' => 'timezone',
|
||||||
|
'code' => 'code',
|
||||||
|
'permissions' => 'permissions',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -47,5 +47,12 @@
|
|||||||
"Profile saved successfully": "Профиль успешно сохранен",
|
"Profile saved successfully": "Профиль успешно сохранен",
|
||||||
"The password has been changed": "Пароль был изменен",
|
"The password has been changed": "Пароль был изменен",
|
||||||
"Default": "По умолчанию",
|
"Default": "По умолчанию",
|
||||||
"The settings have been saved": "Настройки были сохранены"
|
"The settings have been saved": "Настройки были сохранены",
|
||||||
|
"Access is denied": "Доступ запрещён!",
|
||||||
|
"Delete": "Удалить",
|
||||||
|
"Do you want to delete?": "Вы хотите удалить?",
|
||||||
|
"Create": "Создать",
|
||||||
|
"The group was successfully created": "Группа успешно создана",
|
||||||
|
"The group was successfully updated": "Группа успешно обновлена",
|
||||||
|
"The group has been deleted": "Группа была удалена"
|
||||||
}
|
}
|
||||||
|
9
lang/ru/permissions.php
Normal file
9
lang/ru/permissions.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Role' => 'Группа пользователей',
|
||||||
|
'Allowed to watch' => 'Разрешено смотреть',
|
||||||
|
'Allowed to create' => 'Разрешено создать',
|
||||||
|
'Allowed to edit' => 'Разрешено редактировать',
|
||||||
|
'Allowed to delete' => 'Разрешено удалять',
|
||||||
|
];
|
7
lang/ru/sections.php
Normal file
7
lang/ru/sections.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Dashboard' => 'Dashboard',
|
||||||
|
'User group' => 'Группа пользователей',
|
||||||
|
];
|
||||||
|
|
@ -141,6 +141,7 @@ return [
|
|||||||
'uppercase' => 'Значение поля :attribute должно быть в верхнем регистре.',
|
'uppercase' => 'Значение поля :attribute должно быть в верхнем регистре.',
|
||||||
'url' => 'Значение поля :attribute имеет ошибочный формат URL.',
|
'url' => 'Значение поля :attribute имеет ошибочный формат URL.',
|
||||||
'uuid' => 'Значение поля :attribute должно быть корректным UUID.',
|
'uuid' => 'Значение поля :attribute должно быть корректным UUID.',
|
||||||
|
'no_type' => 'Значение поля :attribute может использовать только: :type.',
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'address' => 'адрес',
|
'address' => 'адрес',
|
||||||
'age' => 'возраст',
|
'age' => 'возраст',
|
||||||
@ -215,5 +216,7 @@ return [
|
|||||||
'year' => 'год',
|
'year' => 'год',
|
||||||
'lang' => 'язык',
|
'lang' => 'язык',
|
||||||
'timezone' => 'часовой пояс',
|
'timezone' => 'часовой пояс',
|
||||||
|
'code' => 'код',
|
||||||
|
'permissions' => 'разрешения',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<div class="pt-3">
|
||||||
|
<div class="h5 pb-3">{{ $title }}</div>
|
||||||
|
@error($requestName)
|
||||||
|
<span class="invalid-feedback d-block pb-3">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
@foreach($permissions as $mainPermission)
|
||||||
|
<div class="row ps-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<span class="h6 fw-bold">{{ $mainPermission->getTitle() }}</span>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
@foreach($mainPermission->getPermissions() as $keyPermission => $permission)
|
||||||
|
<li class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="{{ $mainPermission->formatValue($keyPermission) }}" name="{{ $name }}" @checked($role->is_admin || $value->search($mainPermission->formatValue($keyPermission)) !== false) @disabled($role->is_admin) id="form-permission-{{ $requestName }}-{{ $mainPermission->name }}-{{ $keyPermission }}">
|
||||||
|
<label class="form-check-label" for="form-permission-{{ $requestName }}-{{ $mainPermission->name }}-{{ $keyPermission }}">
|
||||||
|
{{ $permission }}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
@ -7,12 +7,28 @@
|
|||||||
<span class="mt-1 ms-1 sidebar-text">Captcha service</span>
|
<span class="mt-1 ms-1 sidebar-text">Captcha service</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item {{ request()->route()->named('home') ? 'active' : '' }}">
|
<li @class([
|
||||||
|
'nav-item',
|
||||||
|
'active' => request()->route()->named('home')
|
||||||
|
])>
|
||||||
<a href="{{ route('home') }}" class="nav-link">
|
<a href="{{ route('home') }}" class="nav-link">
|
||||||
<span class="sidebar-icon">
|
<span class="sidebar-icon">
|
||||||
<svg class="icon icon-xs me-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path><path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z"></path></svg>
|
<svg class="icon icon-xs me-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path><path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z"></path></svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="sidebar-text">Dashboard</span>
|
<span class="sidebar-text">{{ __('sections.Dashboard') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@can('viewAny', \App\Models\Role::class)
|
||||||
|
<li @class([
|
||||||
|
'nav-item',
|
||||||
|
'active' => request()->route()->named('roles.*'),
|
||||||
|
])>
|
||||||
|
<a href="{{ route('roles.index') }}" class="nav-link">
|
||||||
|
<span class="sidebar-icon">
|
||||||
|
<svg class="icon icon-xs me-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"></path></svg>
|
||||||
|
</span>
|
||||||
|
<span class="sidebar-text">{{ __('sections.User group') }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endcan
|
||||||
</ul>
|
</ul>
|
||||||
|
5
resources/views/private/roles/_from.blade.php
Normal file
5
resources/views/private/roles/_from.blade.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@csrf
|
||||||
|
<x-private.forms.input :title="__('validation.attributes.name')" name="name" type="text" :value="$role->name" required autofocus />
|
||||||
|
<x-private.forms.permissions_for_role :title="__('validation.attributes.permissions')" name="permissions[]" :value="$role->permissions->pluck('permission')->toArray()" :role="$role" />
|
||||||
|
<x-private.forms.input :title="__('validation.attributes.code')" name="code" type="text" :value="$role->code" :disabled="!empty($role->id)" required />
|
||||||
|
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
8
resources/views/private/roles/_top.blade.php
Normal file
8
resources/views/private/roles/_top.blade.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@can('create', \App\Models\Role::class)
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{{ route('roles.create') }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||||
|
{{ __('Create') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endcan
|
16
resources/views/private/roles/create.blade.php
Normal file
16
resources/views/private/roles/create.blade.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@section('meta_title', __('sections.User group'))
|
||||||
|
@section('h1', __('sections.User group'))
|
||||||
|
<x-private.layout>
|
||||||
|
@include('private.roles._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="{{ route('roles.store') }}">
|
||||||
|
@include('private.roles._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-private.layout>
|
17
resources/views/private/roles/edit.blade.php
Normal file
17
resources/views/private/roles/edit.blade.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
@section('meta_title', __('sections.User group'))
|
||||||
|
@section('h1', __('sections.User group'))
|
||||||
|
<x-private.layout>
|
||||||
|
@include('private.roles._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="{{ route('roles.update', $role) }}">
|
||||||
|
@method('PUT')
|
||||||
|
@include('private.roles._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-private.layout>
|
54
resources/views/private/roles/index.blade.php
Normal file
54
resources/views/private/roles/index.blade.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
@section('meta_title', __('sections.User group'))
|
||||||
|
@section('h1', __('sections.User group'))
|
||||||
|
<x-private.layout>
|
||||||
|
@include('private.roles._top')
|
||||||
|
<div class="card border-0 shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.name') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.code') }}</th>
|
||||||
|
<th class="border-0 rounded-end" style="width: 150px"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($roles as $role)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('roles.edit', $role) }}" class="fw-bold">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||||
|
</svg>
|
||||||
|
{{ $role->name }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ $role->code }}</td>
|
||||||
|
<td>
|
||||||
|
@if($role->is_remove)
|
||||||
|
<form method="post" action="{{ route('roles.destroy', $role) }}">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-danger click-confirm">
|
||||||
|
{{ __('Delete') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="card-footer border-0 d-flex flex-column flex-lg-row align-items-center justify-content-between">
|
||||||
|
<nav aria-label="Пример навигации по странице">
|
||||||
|
{{ $roles->links() }}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@push('scripts')
|
||||||
|
@include('private._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
|
||||||
|
@endpush
|
||||||
|
</x-private.layout>
|
@ -28,4 +28,7 @@ Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
|
|||||||
Route::get('settings', [\App\Http\Controllers\Private\ProfileController::class, 'settings'])->name('settings');
|
Route::get('settings', [\App\Http\Controllers\Private\ProfileController::class, 'settings'])->name('settings');
|
||||||
Route::put('settings', [\App\Http\Controllers\Private\ProfileController::class, 'updateSettings'])->name('update-settings');
|
Route::put('settings', [\App\Http\Controllers\Private\ProfileController::class, 'updateSettings'])->name('update-settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::resource('users', \App\Http\Controllers\Private\UsersController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['user' => '[0-9]+']);
|
||||||
|
Route::resource('roles', \App\Http\Controllers\Private\RolesController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['role' => '[0-9]+']);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user