From 4083e2ec5ec685f78eca648b636fa6afcdfaa2b7 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 16 Jul 2023 19:21:09 +0600 Subject: [PATCH] Added the ability to manage a group of users. --- app/Console/Commands/CreateUserAdmin.php | 2 +- app/Contracts/Search.php | 18 ++ app/Contracts/ServiceResultError.php | 4 +- app/Dto/Builder/Role.php | 10 ++ app/Dto/QuerySettingsDto.php | 27 +++ app/Dto/Request/Private/Role/Index.php | 24 +++ app/Dto/Request/Private/Role/StoreUpdate.php | 29 +++ app/Enums/Permission.php | 52 ++++++ ...SyncPermissionsCommandHandlerException.php | 8 + .../Controllers/Private/RolesController.php | 92 ++++++++++ .../Requests/Private/Roles/IndexRequest.php | 30 ++++ .../Private/Roles/StoreUpdateRequest.php | 37 ++++ app/Models/RolePermission.php | 21 +++ app/Policies/Policy.php | 24 +++ app/Policies/RolePolicy.php | 34 ++++ app/Providers/AppServiceProvider.php | 9 + app/Providers/AuthServiceProvider.php | 2 +- app/Repositories/RoleRepository.php | 40 ++++- app/Rules/Permission.php | 55 ++++++ app/ServiceResults/ServiceResultError.php | 9 + app/ServiceResults/StoreUpdateResult.php | 23 +++ app/Services/Private/RoleService.php | 165 ++++++++++++++++++ app/Services/Role/BuilderCommand.php | 15 ++ app/Services/Role/RoleCommandHandler.php | 26 +++ .../RoleSyncPermissionsCommandHandler.php | 57 ++++++ .../Search/CreateSearchInstanceCommand.php | 19 ++ app/Services/Search/Search.php | 80 +++++++++ app/Services/Service.php | 7 + .../Private/Forms/PermissionsForRole.php | 62 +++++++ lang/en.json | 9 +- lang/en/permissions.php | 9 + lang/en/sections.php | 6 + lang/en/validation.php | 3 + lang/ru.json | 9 +- lang/ru/permissions.php | 9 + lang/ru/sections.php | 7 + lang/ru/validation.php | 3 + .../forms/permissions_for_role.blade.php | 24 +++ .../private/layout/_navigation.blade.php | 20 ++- resources/views/private/roles/_from.blade.php | 5 + resources/views/private/roles/_top.blade.php | 8 + .../views/private/roles/create.blade.php | 16 ++ resources/views/private/roles/edit.blade.php | 17 ++ resources/views/private/roles/index.blade.php | 54 ++++++ routes/web.php | 3 + 45 files changed, 1173 insertions(+), 10 deletions(-) create mode 100644 app/Contracts/Search.php create mode 100644 app/Dto/Builder/Role.php create mode 100644 app/Dto/QuerySettingsDto.php create mode 100644 app/Dto/Request/Private/Role/Index.php create mode 100644 app/Dto/Request/Private/Role/StoreUpdate.php create mode 100644 app/Enums/Permission.php create mode 100644 app/Exceptions/Rule/RoleSyncPermissionsCommandHandlerException.php create mode 100644 app/Http/Controllers/Private/RolesController.php create mode 100644 app/Http/Requests/Private/Roles/IndexRequest.php create mode 100644 app/Http/Requests/Private/Roles/StoreUpdateRequest.php create mode 100644 app/Models/RolePermission.php create mode 100644 app/Policies/Policy.php create mode 100644 app/Policies/RolePolicy.php create mode 100644 app/Rules/Permission.php create mode 100644 app/ServiceResults/StoreUpdateResult.php create mode 100644 app/Services/Private/RoleService.php create mode 100644 app/Services/Role/BuilderCommand.php create mode 100644 app/Services/Role/RoleCommandHandler.php create mode 100644 app/Services/Role/RoleSyncPermissionsCommandHandler.php create mode 100644 app/Services/Search/CreateSearchInstanceCommand.php create mode 100644 app/Services/Search/Search.php create mode 100644 app/View/Components/Private/Forms/PermissionsForRole.php create mode 100644 lang/en/permissions.php create mode 100644 lang/en/sections.php create mode 100644 lang/ru/permissions.php create mode 100644 lang/ru/sections.php create mode 100644 resources/views/private/components/forms/permissions_for_role.blade.php create mode 100644 resources/views/private/roles/_from.blade.php create mode 100644 resources/views/private/roles/_top.blade.php create mode 100644 resources/views/private/roles/create.blade.php create mode 100644 resources/views/private/roles/edit.blade.php create mode 100644 resources/views/private/roles/index.blade.php diff --git a/app/Console/Commands/CreateUserAdmin.php b/app/Console/Commands/CreateUserAdmin.php index 518ddbe..c01aa8d 100644 --- a/app/Console/Commands/CreateUserAdmin.php +++ b/app/Console/Commands/CreateUserAdmin.php @@ -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.'); } diff --git a/app/Contracts/Search.php b/app/Contracts/Search.php new file mode 100644 index 0000000..20a3841 --- /dev/null +++ b/app/Contracts/Search.php @@ -0,0 +1,18 @@ +limit; + } + + public function getPage(): int + { + return $this->page; + } + + public function getQueryWith(): array + { + return $this->queryWith; + } +} diff --git a/app/Dto/Request/Private/Role/Index.php b/app/Dto/Request/Private/Role/Index.php new file mode 100644 index 0000000..96e2c7d --- /dev/null +++ b/app/Dto/Request/Private/Role/Index.php @@ -0,0 +1,24 @@ +roleBuilderDto; + } + + public function getPage(): int + { + return $this->page; + } +} diff --git a/app/Dto/Request/Private/Role/StoreUpdate.php b/app/Dto/Request/Private/Role/StoreUpdate.php new file mode 100644 index 0000000..f6ffde2 --- /dev/null +++ b/app/Dto/Request/Private/Role/StoreUpdate.php @@ -0,0 +1,29 @@ +name; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function getPermissions(): array + { + return $this->permissions; + } +} diff --git a/app/Enums/Permission.php b/app/Enums/Permission.php new file mode 100644 index 0000000..eb8e808 --- /dev/null +++ b/app/Enums/Permission.php @@ -0,0 +1,52 @@ +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'), + ]; + } +} diff --git a/app/Exceptions/Rule/RoleSyncPermissionsCommandHandlerException.php b/app/Exceptions/Rule/RoleSyncPermissionsCommandHandlerException.php new file mode 100644 index 0000000..0919d42 --- /dev/null +++ b/app/Exceptions/Rule/RoleSyncPermissionsCommandHandlerException.php @@ -0,0 +1,8 @@ +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()); + } +} diff --git a/app/Http/Requests/Private/Roles/IndexRequest.php b/app/Http/Requests/Private/Roles/IndexRequest.php new file mode 100644 index 0000000..110b4e1 --- /dev/null +++ b/app/Http/Requests/Private/Roles/IndexRequest.php @@ -0,0 +1,30 @@ +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) + ); + } +} diff --git a/app/Http/Requests/Private/Roles/StoreUpdateRequest.php b/app/Http/Requests/Private/Roles/StoreUpdateRequest.php new file mode 100644 index 0000000..734feaf --- /dev/null +++ b/app/Http/Requests/Private/Roles/StoreUpdateRequest.php @@ -0,0 +1,37 @@ + ['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', []) + ); + } +} diff --git a/app/Models/RolePermission.php b/app/Models/RolePermission.php new file mode 100644 index 0000000..5709ffb --- /dev/null +++ b/app/Models/RolePermission.php @@ -0,0 +1,21 @@ +permission] ?? ''; + } +} diff --git a/app/Policies/Policy.php b/app/Policies/Policy.php new file mode 100644 index 0000000..1ba6167 --- /dev/null +++ b/app/Policies/Policy.php @@ -0,0 +1,24 @@ +is_admin) { + return true; + } + + return null; + } +} diff --git a/app/Policies/RolePolicy.php b/app/Policies/RolePolicy.php new file mode 100644 index 0000000..2b3f208 --- /dev/null +++ b/app/Policies/RolePolicy.php @@ -0,0 +1,34 @@ +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'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index cd4b73d..d7a5122 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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(); } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index dafcbee..f4c0ae9 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -13,7 +13,7 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - // 'App\Models\Model' => 'App\Policies\ModelPolicy', + \App\Models\Role::class => \App\Policies\RolePolicy::class, ]; /** diff --git a/app/Repositories/RoleRepository.php b/app/Repositories/RoleRepository.php index cba64f6..5977894 100644 --- a/app/Repositories/RoleRepository.php +++ b/app/Repositories/RoleRepository.php @@ -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(); } } diff --git a/app/Rules/Permission.php b/app/Rules/Permission.php new file mode 100644 index 0000000..101512f --- /dev/null +++ b/app/Rules/Permission.php @@ -0,0 +1,55 @@ +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; + } + } +} diff --git a/app/ServiceResults/ServiceResultError.php b/app/ServiceResults/ServiceResultError.php index 26f310f..4ff8b19 100644 --- a/app/ServiceResults/ServiceResultError.php +++ b/app/ServiceResults/ServiceResultError.php @@ -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 [ diff --git a/app/ServiceResults/StoreUpdateResult.php b/app/ServiceResults/StoreUpdateResult.php new file mode 100644 index 0000000..472371d --- /dev/null +++ b/app/ServiceResults/StoreUpdateResult.php @@ -0,0 +1,23 @@ +model; + } + + public function getMessage(): string + { + return $this->message; + } +} diff --git a/app/Services/Private/RoleService.php b/app/Services/Private/RoleService.php new file mode 100644 index 0000000..5a3ba7c --- /dev/null +++ b/app/Services/Private/RoleService.php @@ -0,0 +1,165 @@ +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(), + ]; + } +} diff --git a/app/Services/Role/BuilderCommand.php b/app/Services/Role/BuilderCommand.php new file mode 100644 index 0000000..d37d323 --- /dev/null +++ b/app/Services/Role/BuilderCommand.php @@ -0,0 +1,15 @@ +update($data); + $role->touch(); + + return $role; + } + + public function handleDestroy(Role $role): void + { + $role->delete(); + } +} diff --git a/app/Services/Role/RoleSyncPermissionsCommandHandler.php b/app/Services/Role/RoleSyncPermissionsCommandHandler.php new file mode 100644 index 0000000..7846fd9 --- /dev/null +++ b/app/Services/Role/RoleSyncPermissionsCommandHandler.php @@ -0,0 +1,57 @@ +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; + } +} diff --git a/app/Services/Search/CreateSearchInstanceCommand.php b/app/Services/Search/CreateSearchInstanceCommand.php new file mode 100644 index 0000000..3f775ee --- /dev/null +++ b/app/Services/Search/CreateSearchInstanceCommand.php @@ -0,0 +1,19 @@ +abstract($query); + } +} diff --git a/app/Services/Search/Search.php b/app/Services/Search/Search.php new file mode 100644 index 0000000..d37c506 --- /dev/null +++ b/app/Services/Search/Search.php @@ -0,0 +1,80 @@ +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(); + } +} diff --git a/app/Services/Service.php b/app/Services/Service.php index f7bb5f9..e0d7d01 100644 --- a/app/Services/Service.php +++ b/app/Services/Service.php @@ -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); diff --git a/app/View/Components/Private/Forms/PermissionsForRole.php b/app/View/Components/Private/Forms/PermissionsForRole.php new file mode 100644 index 0000000..3e3fcff --- /dev/null +++ b/app/View/Components/Private/Forms/PermissionsForRole.php @@ -0,0 +1,62 @@ +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(), + ]); + } + +} diff --git a/lang/en.json b/lang/en.json index 9aea158..d8e3f33 100644 --- a/lang/en.json +++ b/lang/en.json @@ -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" + "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" } diff --git a/lang/en/permissions.php b/lang/en/permissions.php new file mode 100644 index 0000000..2bdb95e --- /dev/null +++ b/lang/en/permissions.php @@ -0,0 +1,9 @@ + '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', +]; diff --git a/lang/en/sections.php b/lang/en/sections.php new file mode 100644 index 0000000..4d55602 --- /dev/null +++ b/lang/en/sections.php @@ -0,0 +1,6 @@ + 'Dashboard', + 'User group' => 'User group', +]; diff --git a/lang/en/validation.php b/lang/en/validation.php index bfda1c8..694f5ec 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -141,6 +141,7 @@ return [ 'uppercase' => 'The :attribute must be uppercase.', 'url' => 'The :attribute format is invalid.', 'uuid' => 'The :attribute must be a valid UUID.', + 'no_type' => 'The :attribute can only use: :type.', 'attributes' => [ 'address' => 'address', 'age' => 'age', @@ -215,5 +216,7 @@ return [ 'year' => 'year', 'lang' => 'language', 'timezone' => 'timezone', + 'code' => 'code', + 'permissions' => 'permissions', ], ]; diff --git a/lang/ru.json b/lang/ru.json index 0fbc0e1..51f9cff 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -47,5 +47,12 @@ "Profile saved successfully": "Профиль успешно сохранен", "The password has been changed": "Пароль был изменен", "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": "Группа была удалена" } diff --git a/lang/ru/permissions.php b/lang/ru/permissions.php new file mode 100644 index 0000000..05dd129 --- /dev/null +++ b/lang/ru/permissions.php @@ -0,0 +1,9 @@ + 'Группа пользователей', + 'Allowed to watch' => 'Разрешено смотреть', + 'Allowed to create' => 'Разрешено создать', + 'Allowed to edit' => 'Разрешено редактировать', + 'Allowed to delete' => 'Разрешено удалять', +]; diff --git a/lang/ru/sections.php b/lang/ru/sections.php new file mode 100644 index 0000000..ae16063 --- /dev/null +++ b/lang/ru/sections.php @@ -0,0 +1,7 @@ + 'Dashboard', + 'User group' => 'Группа пользователей', +]; + diff --git a/lang/ru/validation.php b/lang/ru/validation.php index 670d101..058b5fd 100644 --- a/lang/ru/validation.php +++ b/lang/ru/validation.php @@ -141,6 +141,7 @@ return [ 'uppercase' => 'Значение поля :attribute должно быть в верхнем регистре.', 'url' => 'Значение поля :attribute имеет ошибочный формат URL.', 'uuid' => 'Значение поля :attribute должно быть корректным UUID.', + 'no_type' => 'Значение поля :attribute может использовать только: :type.', 'attributes' => [ 'address' => 'адрес', 'age' => 'возраст', @@ -215,5 +216,7 @@ return [ 'year' => 'год', 'lang' => 'язык', 'timezone' => 'часовой пояс', + 'code' => 'код', + 'permissions' => 'разрешения', ], ]; diff --git a/resources/views/private/components/forms/permissions_for_role.blade.php b/resources/views/private/components/forms/permissions_for_role.blade.php new file mode 100644 index 0000000..6890081 --- /dev/null +++ b/resources/views/private/components/forms/permissions_for_role.blade.php @@ -0,0 +1,24 @@ +
+
{{ $title }}
+ @error($requestName) + {{ $message }} + @enderror + @foreach($permissions as $mainPermission) +
+
+ {{ $mainPermission->getTitle() }} +
+
    + @foreach($mainPermission->getPermissions() as $keyPermission => $permission) +
  • + is_admin || $value->search($mainPermission->formatValue($keyPermission)) !== false) @disabled($role->is_admin) id="form-permission-{{ $requestName }}-{{ $mainPermission->name }}-{{ $keyPermission }}"> + +
  • + @endforeach +
+
+
+ @endforeach +
diff --git a/resources/views/private/layout/_navigation.blade.php b/resources/views/private/layout/_navigation.blade.php index 7dd830a..8dea757 100644 --- a/resources/views/private/layout/_navigation.blade.php +++ b/resources/views/private/layout/_navigation.blade.php @@ -7,12 +7,28 @@ Captcha service -
  • request()->route()->named('home') + ])> - Dashboard + {{ __('sections.Dashboard') }}
  • + @can('viewAny', \App\Models\Role::class) +
  • request()->route()->named('roles.*'), + ])> + + + + + {{ __('sections.User group') }} + +
  • + @endcan diff --git a/resources/views/private/roles/_from.blade.php b/resources/views/private/roles/_from.blade.php new file mode 100644 index 0000000..c3eba00 --- /dev/null +++ b/resources/views/private/roles/_from.blade.php @@ -0,0 +1,5 @@ +@csrf + + + + diff --git a/resources/views/private/roles/_top.blade.php b/resources/views/private/roles/_top.blade.php new file mode 100644 index 0000000..900d2fe --- /dev/null +++ b/resources/views/private/roles/_top.blade.php @@ -0,0 +1,8 @@ +@can('create', \App\Models\Role::class) + +@endcan diff --git a/resources/views/private/roles/create.blade.php b/resources/views/private/roles/create.blade.php new file mode 100644 index 0000000..a67e373 --- /dev/null +++ b/resources/views/private/roles/create.blade.php @@ -0,0 +1,16 @@ +@section('meta_title', __('sections.User group')) +@section('h1', __('sections.User group')) + + @include('private.roles._top') +
    +
    +
    +
    +
    + @include('private.roles._from') +
    +
    +
    +
    +
    +
    diff --git a/resources/views/private/roles/edit.blade.php b/resources/views/private/roles/edit.blade.php new file mode 100644 index 0000000..0000999 --- /dev/null +++ b/resources/views/private/roles/edit.blade.php @@ -0,0 +1,17 @@ +@section('meta_title', __('sections.User group')) +@section('h1', __('sections.User group')) + + @include('private.roles._top') +
    +
    +
    +
    +
    + @method('PUT') + @include('private.roles._from') +
    +
    +
    +
    +
    +
    diff --git a/resources/views/private/roles/index.blade.php b/resources/views/private/roles/index.blade.php new file mode 100644 index 0000000..66f46b9 --- /dev/null +++ b/resources/views/private/roles/index.blade.php @@ -0,0 +1,54 @@ +@section('meta_title', __('sections.User group')) +@section('h1', __('sections.User group')) + + @include('private.roles._top') +
    +
    +
    + + + + + + + + + + @foreach($roles as $role) + + + + + + @endforeach + +
    {{ __('validation.attributes.name') }}{{ __('validation.attributes.code') }}
    + + + + + {{ $role->name }} + + {{ $role->code }} + @if($role->is_remove) +
    + @csrf + @method('DELETE') + +
    + @endif +
    + +
    +
    +
    + @push('scripts') + @include('private._scripts._click-confirm', ['alert' => __('Do you want to delete?')]) + @endpush +
    diff --git a/routes/web.php b/routes/web.php index 5077d13..79cab7d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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::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]+']); });