Init git.
This commit is contained in:
commit
a8d656148a
10
.env.example
Normal file
10
.env.example
Normal file
@ -0,0 +1,10 @@
|
||||
DOCKER_APP_PORT=8080
|
||||
DOCKER_CAPTCHA_PORT=8081
|
||||
DOCKER_CAPTCHA_WEBSOCKET_PORT=8082
|
||||
DOCKER_DB_PORT=3306
|
||||
MYSQL_ROOT_PASSWORD=root_pass
|
||||
DB_DATABASE=registry
|
||||
DB_USERNAME=registry
|
||||
DB_PASSWORD=registry_pass
|
||||
UID=1000
|
||||
GID=1000
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.env
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
.phpunit.result.cache
|
||||
|
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 - 2024 Leonid Nikitin (kor-elf)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
## О проекте MDHub
|
||||
|
||||
Docker Hub отличный сервис, но, а что если он Вас заблокирует или перестанет по другим причинам работать?! Я так же задумался, а вдруг образы от моих проектов, кто-то не сможет скачать с Docker Hub? Цель проекта сделать возможность загружать свои образы в своё хранилища и разрешить скачивать их без авторизации. Так же была возможность загружать и приватные образы, где нужна авторизация. Типа Docker Hub на минималках.
|
||||
|
||||
[Сайт проекта](https://my-docker-repository.projects.kor-elf.net/)
|
||||
|
||||
## Зависимости
|
||||
|
||||
php 8.3 (модули: redis)
|
||||
|
||||
redis
|
||||
|
||||
mysql 8
|
||||
|
||||
[Service Captcha](https://git.kor-elf.net/kor-elf/service-captcha)
|
||||
|
||||
## Лицензия
|
||||
|
||||
[MIT license](https://opensource.org/licenses/MIT).
|
14
app/.dockerignore
Normal file
14
app/.dockerignore
Normal file
@ -0,0 +1,14 @@
|
||||
**/.env
|
||||
**/*.env
|
||||
**/.env.example
|
||||
**/storage/app/*
|
||||
**/storage/debugbar
|
||||
**/storage/framework/cache/*
|
||||
**/storage/framework/sessions/*
|
||||
**/storage/framework/views/*
|
||||
**/storage/framework/testing/*
|
||||
**/storage/logs/*
|
||||
**/vendor/
|
||||
**/node_modules/
|
||||
**/public/build/
|
||||
**/public/storage
|
18
app/application/.editorconfig
Normal file
18
app/application/.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
76
app/application/.env.example
Normal file
76
app/application/.env.example
Normal file
@ -0,0 +1,76 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:LAXuVFnfcrPlPcYftpV/hJ7mEO1TlbriMaDN7hT5WOo=
|
||||
APP_DEBUG=true
|
||||
DEBUGBAR_OPEN_STORAGE=true
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=
|
||||
|
||||
APP_CAPTCHA=false
|
||||
CAPTCHA_API_DOMAIN=http://your-domain-captcha-or-IP:8081
|
||||
CAPTCHA_PRIVATE_TOKEN=
|
||||
CAPTCHA_STATIC_PATH=http://your-domain-captcha-or-IP:8081/captcha
|
||||
CAPTCHA_PUBLIC_TOKEN=
|
||||
|
||||
APP_FORCE_HTTPS=false
|
||||
|
||||
APP_DEFAULT_LOCALE=ru
|
||||
APP_FAKER_LOCALE=ru_RU
|
||||
APP_DEFAULT_USER_TIMEZONE=Asia/Almaty
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
APP_MAINTENANCE_STORE=database
|
||||
|
||||
LOGIN_MAX_REQUEST=50
|
||||
LOGIN_MAX_EMAIL_REQUEST=10
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=registry
|
||||
DB_USERNAME=registry
|
||||
DB_PASSWORD=registry_pass
|
||||
|
||||
SESSION_DRIVER=redis
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=true
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=app-redis
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
11
app/application/.gitattributes
vendored
Normal file
11
app/application/.gitattributes
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
20
app/application/.gitignore
vendored
Normal file
20
app/application/.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
66
app/application/README.md
Normal file
66
app/application/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## About Laravel
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
|
||||
## Learning Laravel
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
|
||||
|
||||
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
|
||||
### Premium Partners
|
||||
|
||||
- **[Vehikl](https://vehikl.com/)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[WebReinvent](https://webreinvent.com/)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Jump24](https://jump24.co.uk)**
|
||||
- **[Redberry](https://redberry.international/laravel/)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
- **[byte5](https://byte5.de)**
|
||||
- **[OP.GG](https://op.gg)**
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
95
app/application/app/Console/Commands/CreateUserAdmin.php
Normal file
95
app/application/app/Console/Commands/CreateUserAdmin.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Dto\Service\User\ManyRoleDto;
|
||||
use App\Enums\SystemRole;
|
||||
use App\Repositories\RoleRepository;
|
||||
use App\Rules\Username;
|
||||
use App\Services\User\UserCommandHandler;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
final class CreateUserAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:create-user-admin {email} {username} {password}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create admin user.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(UserCommandHandler $userCommandHandler, RoleRepository $roleRepository): void
|
||||
{
|
||||
$validator = $this->getData();
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->errorMessageAndStop($validator->errors()->all());
|
||||
}
|
||||
$data = $validator->valid();
|
||||
|
||||
try {
|
||||
$role = $roleRepository->getRoleByCode(SystemRole::Admin->value);
|
||||
if (is_null($role)) {
|
||||
$this->errorMessageAndStop('Administrator role not found.');
|
||||
}
|
||||
DB::transaction(function () use($data, $userCommandHandler, $role) {
|
||||
$data['name'] = 'Administrator';
|
||||
$user = $userCommandHandler->handleStore($data, $data['password']);
|
||||
$userCommandHandler->handleConfirmationByEmail($user);
|
||||
$roles = new ManyRoleDto([$role->id]);
|
||||
$userCommandHandler->handleSyncRoles($user, $roles);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessageAndStop($e->getMessage());
|
||||
}
|
||||
|
||||
$this->info('The command was successful!');
|
||||
}
|
||||
|
||||
private function getData(): Validator
|
||||
{
|
||||
return ValidatorFacade::make([
|
||||
'email' => $this->argument('email'),
|
||||
'username' => $this->argument('username'),
|
||||
'password' => $this->argument('password'),
|
||||
], [
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||
'username' => ['required', 'string', new Username(), 'unique:users,username'],
|
||||
'password' => ['required', Password::default()],
|
||||
]);
|
||||
}
|
||||
|
||||
private function stop(): never
|
||||
{
|
||||
exit;
|
||||
}
|
||||
|
||||
private function errorMessageAndStop(string | array $error): never
|
||||
{
|
||||
$this->info('User not created. See error messages below:');
|
||||
|
||||
if (is_array($error)) {
|
||||
foreach ($error as $err) {
|
||||
$this->error($err);
|
||||
}
|
||||
} else {
|
||||
$this->error($error);
|
||||
}
|
||||
|
||||
$this->stop();
|
||||
}
|
||||
}
|
10
app/application/app/Contracts/FormRequestDto.php
Normal file
10
app/application/app/Contracts/FormRequestDto.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
|
||||
interface FormRequestDto
|
||||
{
|
||||
public function getDto(): Dto;
|
||||
}
|
18
app/application/app/Contracts/Search.php
Normal file
18
app/application/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;
|
||||
}
|
9
app/application/app/Contracts/ServiceResult.php
Normal file
9
app/application/app/Contracts/ServiceResult.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface ServiceResult
|
||||
{
|
||||
public function isSuccess(): bool;
|
||||
public function isError(): bool;
|
||||
}
|
12
app/application/app/Contracts/ServiceResultError.php
Normal file
12
app/application/app/Contracts/ServiceResultError.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
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/application/app/Dto/Builder/Role.php
Normal file
10
app/application/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(
|
||||
|
||||
) { }
|
||||
}
|
10
app/application/app/Dto/Builder/User.php
Normal file
10
app/application/app/Dto/Builder/User.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class User
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
27
app/application/app/Dto/QuerySettingsDto.php
Normal file
27
app/application/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;
|
||||
}
|
||||
}
|
21
app/application/app/Dto/Service/Admin/Role/Index.php
Normal file
21
app/application/app/Dto/Service/Admin/Role/Index.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Role;
|
||||
|
||||
use App\Dto\Builder\Role;
|
||||
use App\Dto\Service\Pages;
|
||||
|
||||
final readonly class Index extends Pages
|
||||
{
|
||||
public function __construct(
|
||||
private Role $roleBuilderDto,
|
||||
int $page
|
||||
) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
|
||||
public function getRoleBuilderDto(): Role
|
||||
{
|
||||
return $this->roleBuilderDto;
|
||||
}
|
||||
}
|
29
app/application/app/Dto/Service/Admin/Role/StoreUpdate.php
Normal file
29
app/application/app/Dto/Service/Admin/Role/StoreUpdate.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Role;
|
||||
|
||||
use App\Dto\Service\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;
|
||||
}
|
||||
}
|
21
app/application/app/Dto/Service/Admin/User/Index.php
Normal file
21
app/application/app/Dto/Service/Admin/User/Index.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\User;
|
||||
|
||||
use App\Dto\Builder\User;
|
||||
use App\Dto\Service\Pages;
|
||||
|
||||
final readonly class Index extends Pages
|
||||
{
|
||||
public function __construct(
|
||||
private User $userBuilderDto,
|
||||
int $page
|
||||
) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
|
||||
public function getUserBuilderDto(): User
|
||||
{
|
||||
return $this->userBuilderDto;
|
||||
}
|
||||
}
|
48
app/application/app/Dto/Service/Admin/User/StoreUpdate.php
Normal file
48
app/application/app/Dto/Service/Admin/User/StoreUpdate.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\User;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Dto\Service\User\ManyRoleDto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $email,
|
||||
private string $username,
|
||||
private bool $isActive,
|
||||
private ManyRoleDto $roles,
|
||||
private ?string $password = null
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getRoles(): ManyRoleDto
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->isActive;
|
||||
}
|
||||
}
|
27
app/application/app/Dto/Service/Authorization.php
Normal file
27
app/application/app/Dto/Service/Authorization.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service;
|
||||
|
||||
final readonly class Authorization extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $email,
|
||||
private string $password,
|
||||
private bool $remember = false
|
||||
) { }
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getRemember(): bool
|
||||
{
|
||||
return $this->remember;
|
||||
}
|
||||
}
|
8
app/application/app/Dto/Service/Dto.php
Normal file
8
app/application/app/Dto/Service/Dto.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service;
|
||||
|
||||
abstract readonly class Dto
|
||||
{
|
||||
|
||||
}
|
15
app/application/app/Dto/Service/Pages.php
Normal file
15
app/application/app/Dto/Service/Pages.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service;
|
||||
|
||||
abstract readonly class Pages extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private int $page
|
||||
) { }
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
17
app/application/app/Dto/Service/Site/Profile/Update.php
Normal file
17
app/application/app/Dto/Service/Site/Profile/Update.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Site\Profile;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
|
||||
final readonly class Update extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Site\Profile;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Enums\Lang;
|
||||
|
||||
final readonly class UpdateSettings extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private ?Lang $lang,
|
||||
private ?string $timezone
|
||||
) { }
|
||||
|
||||
public function getLang(): ?Lang
|
||||
{
|
||||
return $this->lang;
|
||||
}
|
||||
|
||||
public function getTimezone(): ?string
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
}
|
32
app/application/app/Dto/Service/User/ManyRoleDto.php
Normal file
32
app/application/app/Dto/Service/User/ManyRoleDto.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\User;
|
||||
|
||||
use App\Exceptions\Dto\Service\User\ManyRoleDtoException;
|
||||
|
||||
final class ManyRoleDto
|
||||
{
|
||||
private array $roles = [];
|
||||
|
||||
public function __construct(array $roles = []) {
|
||||
foreach ($roles as $role) {
|
||||
if (!is_numeric($role) || is_float($role)) {
|
||||
throw new ManyRoleDtoException('Not an integer: ' . $role . '.');
|
||||
}
|
||||
$this->add((int) $role);
|
||||
}
|
||||
}
|
||||
|
||||
public function add(int $id): void
|
||||
{
|
||||
if ($id < 1) {
|
||||
throw new ManyRoleDtoException('Only Integer > 0.');
|
||||
}
|
||||
$this->roles[] = $id;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
}
|
17
app/application/app/Dto/Service/User/UpdatePassword.php
Normal file
17
app/application/app/Dto/Service/User/UpdatePassword.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\User;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
|
||||
final readonly class UpdatePassword extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $password
|
||||
) { }
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
}
|
46
app/application/app/Enums/Lang.php
Normal file
46
app/application/app/Enums/Lang.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
enum Lang: int
|
||||
{
|
||||
case Ru = 1;
|
||||
case En = 2;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Ru => 'Русский',
|
||||
self::En => 'English'
|
||||
};
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Ru => 'ru',
|
||||
self::En => 'en'
|
||||
};
|
||||
}
|
||||
|
||||
public static function toArray(): array
|
||||
{
|
||||
$choices = [];
|
||||
foreach (self::cases() as $lang) {
|
||||
$choices[] = [
|
||||
'name' => $lang->name,
|
||||
'value' => $lang->value,
|
||||
'title' => $lang->getTitle(),
|
||||
'locale' => $lang->getLocale()
|
||||
];
|
||||
}
|
||||
return $choices;
|
||||
}
|
||||
|
||||
public static function toCollection(): Collection
|
||||
{
|
||||
return collect(self::toArray());
|
||||
}
|
||||
}
|
61
app/application/app/Enums/Permission.php
Normal file
61
app/application/app/Enums/Permission.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum Permission: string
|
||||
{
|
||||
case AdminPanel = 'allow-admin-panel';
|
||||
case Role = 'role';
|
||||
case User = 'user';
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
$permissions = match ($this) {
|
||||
self::AdminPanel => [
|
||||
'view' => __('permissions.Administrative panel allowed'),
|
||||
],
|
||||
default => $this->getBasePermissions()
|
||||
};
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
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'),
|
||||
];
|
||||
}
|
||||
}
|
8
app/application/app/Enums/SystemRole.php
Normal file
8
app/application/app/Enums/SystemRole.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum SystemRole: string
|
||||
{
|
||||
case Admin = 'admin';
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Dto\Service\User;
|
||||
|
||||
final class ManyRoleDtoException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Services\Rule;
|
||||
|
||||
final class RoleSyncPermissionsCommandHandlerException extends \Exception
|
||||
{
|
||||
|
||||
}
|
35
app/application/app/Helpers/Helpers.php
Normal file
35
app/application/app/Helpers/Helpers.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final readonly class Helpers
|
||||
{
|
||||
public static function getTimeZoneList(): Collection
|
||||
{
|
||||
return Cache::rememberForever('timezones_list_collection', function () {
|
||||
$timezone = [];
|
||||
foreach (timezone_identifiers_list(\DateTimeZone::ALL) as $key => $value) {
|
||||
$timezone[$value] = $value . ' (UTC ' . now($value)->format('P') . ')';
|
||||
}
|
||||
return collect($timezone)->sortKeys();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* $name = 'field[key]' return 'field.key'
|
||||
*/
|
||||
public static function formatAttributeNameToRequestName(string $name): string
|
||||
{
|
||||
return Str::of($name)
|
||||
->replace(
|
||||
['.', '[', ']'],
|
||||
['_', '.', ''],
|
||||
)
|
||||
->rtrim('.')
|
||||
->value();
|
||||
}
|
||||
}
|
10
app/application/app/Http/Controllers/Admin/Controller.php
Normal file
10
app/application/app/Http/Controllers/Admin/Controller.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller as BaseController;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class DashboardController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
return view('admin/dashboard/index');
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Admin\Roles\IndexRequest;
|
||||
use App\Http\Requests\Admin\Roles\StoreUpdateRequest;
|
||||
use App\Services\Admin\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('admin/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('admin/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('admin/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('admin.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('admin.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('admin.roles.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
106
app/application/app/Http/Controllers/Admin/UsersController.php
Normal file
106
app/application/app/Http/Controllers/Admin/UsersController.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Admin\Users\IndexRequest;
|
||||
use App\Http\Requests\Admin\Users\StoreUpdateRequest;
|
||||
use App\Http\Requests\Admin\Users\UpdatePasswordRequest;
|
||||
use App\Services\Admin\UserService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class UsersController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserService $userService
|
||||
) { }
|
||||
|
||||
public function index(IndexRequest $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$data = $request->getDto();
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
);
|
||||
|
||||
$result = $this->userService->index($data->getUserBuilderDto(), $querySettingsDto, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('admin/users/index', $result->getData());
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->userService->create($user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('admin/users/create', $result->getData());
|
||||
}
|
||||
|
||||
public function edit(int $id, Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->userService->edit($id, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('admin/users/edit', $result->getData());
|
||||
}
|
||||
|
||||
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->userService->store($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->userService->update($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function updatePassword(int $id, UpdatePasswordRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->userService->updatePassword($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function destroy(int $id, Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->userService->destroy($id, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.users.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
49
app/application/app/Http/Controllers/AuthController.php
Normal file
49
app/application/app/Http/Controllers/AuthController.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\AuthorizationRequest;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
final class AuthController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AuthService $authService
|
||||
) { }
|
||||
|
||||
public function login(): View
|
||||
{
|
||||
return view('login', [
|
||||
'captcha' => config('app.captcha', false),
|
||||
]);
|
||||
}
|
||||
|
||||
public function authorization(AuthorizationRequest $request): RedirectResponse
|
||||
{
|
||||
$authorization = $request->getDto();
|
||||
$result = $this->authService->authorization($authorization);
|
||||
if ($result->isError()) {
|
||||
if ($result->getCode() === Response::HTTP_UNAUTHORIZED) {
|
||||
Log::warning('Unauthorized ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']');
|
||||
}
|
||||
return redirect()->route('login')->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
$request->session()->regenerate();
|
||||
Log::notice('Logged in ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']');
|
||||
return redirect()->route('home');
|
||||
}
|
||||
|
||||
public function logout(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect(route('login'));
|
||||
}
|
||||
}
|
8
app/application/app/Http/Controllers/Controller.php
Normal file
8
app/application/app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
8
app/application/app/Http/Controllers/Site/Controller.php
Normal file
8
app/application/app/Http/Controllers/Site/Controller.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Site;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
13
app/application/app/Http/Controllers/Site/HomeController.php
Normal file
13
app/application/app/Http/Controllers/Site/HomeController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Site;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class HomeController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
return \view('site.home.index');
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Site;
|
||||
|
||||
use App\Enums\Lang;
|
||||
use App\Helpers\Helpers;
|
||||
use App\Http\Requests\Site\Profile\UpdatePasswordRequest;
|
||||
use App\Http\Requests\Site\Profile\UpdateRequest;
|
||||
use App\Http\Requests\Site\Profile\UpdateSettingsRequest;
|
||||
use App\Services\Site\ProfileService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class ProfileController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProfileService $profileService
|
||||
) { }
|
||||
|
||||
public function profile(Request $request): View
|
||||
{
|
||||
return view('site.profile.profile', [
|
||||
'user' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(Request $request): View
|
||||
{
|
||||
return view('site.profile.settings', [
|
||||
'user' => $request->user(),
|
||||
'languages' => Lang::toCollection()->pluck(value: 'title', key: 'value')->toArray(),
|
||||
'timezone' => Helpers::getTimeZoneList()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
|
||||
$result = $this->profileService->update($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function updatePassword(UpdatePasswordRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
|
||||
$result = $this->profileService->updatePassword($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function updateSettings(UpdateSettingsRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
|
||||
$result = $this->profileService->updateSettings($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
return redirect()->route('profile.settings')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
26
app/application/app/Http/Middleware/AdminPanel.php
Normal file
26
app/application/app/Http/Middleware/AdminPanel.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class AdminPanel
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (
|
||||
$request->user() === null || $request->user()->cannot('AdminPanel')
|
||||
) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
18
app/application/app/Http/Middleware/UserIsActive.php
Normal file
18
app/application/app/Http/Middleware/UserIsActive.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class UserIsActive
|
||||
{
|
||||
public function handle(Request $request, \Closure $next)
|
||||
{
|
||||
if ($request->user()->is_active === false) {
|
||||
\abort(Response::HTTP_FORBIDDEN, 'User disabled');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
19
app/application/app/Http/Middleware/UserLocale.php
Normal file
19
app/application/app/Http/Middleware/UserLocale.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
final class UserLocale
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if ($request->user() && $request->user()->lang) {
|
||||
App::setLocale($request->user()->lang->getLocale());
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Roles;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Builder\Role;
|
||||
use App\Dto\Service\Admin\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('admin.roles.index');
|
||||
return [
|
||||
'page' => ['nullable', 'numeric', 'min:1']
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Index
|
||||
{
|
||||
return new Index(
|
||||
roleBuilderDto: new Role(),
|
||||
page: (int) $this->input('page', 1)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Roles;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Admin\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', [])
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Users;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Builder\User;
|
||||
use App\Dto\Service\Admin\User\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('admin.users.index');
|
||||
return [
|
||||
'page' => ['nullable', 'numeric', 'min:1']
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Index
|
||||
{
|
||||
return new Index(
|
||||
userBuilderDto: new User(),
|
||||
page: (int) $this->input('page', 1)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Users;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Admin\User\StoreUpdate;
|
||||
use App\Dto\Service\User\ManyRoleDto;
|
||||
use App\Rules\Username;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
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'],
|
||||
'email' => ['required', 'email', 'max:255'],
|
||||
'username' => ['required', 'string', new Username()],
|
||||
'is_active' => ['required', 'boolean'],
|
||||
'roles' => ['array', Rule::exists('roles', 'id')],
|
||||
];
|
||||
|
||||
if ($this->getMethod() === 'POST') {
|
||||
$rules['password'] = ['required', Password::default()];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
public function getDto(): StoreUpdate
|
||||
{
|
||||
return new StoreUpdate(
|
||||
name: $this->input('name'),
|
||||
email: $this->input('email'),
|
||||
username: $this->input('username'),
|
||||
isActive: (bool) $this->input('is_active', false),
|
||||
roles: new ManyRoleDto($this->input('roles', [])),
|
||||
password: $this->input('password', null),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Users;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\User\UpdatePassword;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
final class UpdatePasswordRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => ['required', Password::default()],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): UpdatePassword
|
||||
{
|
||||
return new UpdatePassword(password: $this->input('password'));
|
||||
}
|
||||
}
|
37
app/application/app/Http/Requests/AuthorizationRequest.php
Normal file
37
app/application/app/Http/Requests/AuthorizationRequest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Authorization;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class AuthorizationRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'email' => ['required', 'email', 'max:255'],
|
||||
'password' => ['required', 'min:3'],
|
||||
'remember' => ['nullable', 'boolean'],
|
||||
];
|
||||
|
||||
if (config('app.captcha', false)) {
|
||||
$rules['captcha-verified'] = ['captcha'];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function getDto(): Authorization
|
||||
{
|
||||
return new Authorization(
|
||||
email: $this->input('email'),
|
||||
password: $this->input('password'),
|
||||
remember: (bool) $this->input('remember', false)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Site\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\User\UpdatePassword;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
final class UpdatePasswordRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => ['required', 'confirmed', Password::default()],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): UpdatePassword
|
||||
{
|
||||
return new UpdatePassword(password: $this->input('password'));
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Site\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Site\Profile\Update;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class UpdateRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Update
|
||||
{
|
||||
return new Update(name: $this->input('name'));
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Site\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Site\Profile\UpdateSettings;
|
||||
use App\Enums\Lang;
|
||||
use App\Helpers\Helpers;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
final class UpdateSettingsRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'lang' => ['nullable', new Enum(Lang::class)],
|
||||
'timezone' => ['nullable', Rule::in(Helpers::getTimeZoneList()->keys()->toArray())]
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): UpdateSettings
|
||||
{
|
||||
$lang = $this->input('lang', null);
|
||||
if (!is_null($lang)) {
|
||||
$lang = Lang::from((int) $lang);
|
||||
}
|
||||
|
||||
return new UpdateSettings(
|
||||
lang: $lang,
|
||||
timezone: $this->input('timezone', null),
|
||||
);
|
||||
}
|
||||
}
|
55
app/application/app/Models/Role.php
Normal file
55
app/application/app/Models/Role.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\SystemRole as SystemRoleEnum;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\hasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
final class Role extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code'
|
||||
];
|
||||
|
||||
public function scopeLatest(Builder $query): Builder
|
||||
{
|
||||
return $query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
public function scopeAlphavit(Builder $query): Builder
|
||||
{
|
||||
return $query->orderBy('name', 'asc');
|
||||
}
|
||||
|
||||
public function permissions(): hasMany
|
||||
{
|
||||
return $this->hasMany(RolePermission::class, 'role_id', 'id');
|
||||
}
|
||||
|
||||
protected function isRemove(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn ($dontRemove) => ( SystemRoleEnum::tryFrom($this->code) === null ),
|
||||
)->shouldCache();
|
||||
}
|
||||
|
||||
protected function isAdmin(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => ( $this->code === SystemRoleEnum::Admin->value ),
|
||||
)->shouldCache();
|
||||
}
|
||||
}
|
21
app/application/app/Models/RolePermission.php
Normal file
21
app/application/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] ?? '';
|
||||
}
|
||||
}
|
102
app/application/app/Models/User.php
Normal file
102
app/application/app/Models/User.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Enums\Lang;
|
||||
use App\Enums\SystemRole;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
final class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The model's default values for attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'is_active' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
'password',
|
||||
'timezone',
|
||||
'lang',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'lang' => Lang::class,
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the user's roles
|
||||
*/
|
||||
public function roles(): belongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Role::class);
|
||||
}
|
||||
|
||||
public function hasRole(string $role): bool
|
||||
{
|
||||
return $this->roles->where('code', $role)->isNotEmpty();
|
||||
}
|
||||
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
return $this->permissions->search($permission) !== false;
|
||||
}
|
||||
|
||||
protected function isAdmin(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->hasRole(SystemRole::Admin->value),
|
||||
)->shouldCache();
|
||||
}
|
||||
|
||||
protected function permissions(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$roles = $this->roles->modelKeys();
|
||||
return RolePermission::whereIn('role_id', $roles)->select('permission')->pluck('permission');
|
||||
},
|
||||
)->shouldCache();
|
||||
}
|
||||
}
|
14
app/application/app/Policies/AdminPanel.php
Normal file
14
app/application/app/Policies/AdminPanel.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Models\User;
|
||||
|
||||
readonly class AdminPanel extends Policy
|
||||
{
|
||||
public function view(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('allow-admin-panel.view');
|
||||
}
|
||||
}
|
24
app/application/app/Policies/Policy.php
Normal file
24
app/application/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;
|
||||
|
||||
final public function before(User $user): ?bool
|
||||
{
|
||||
if ($user->is_active !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->is_admin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
34
app/application/app/Policies/RolePolicy.php
Normal file
34
app/application/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');
|
||||
}
|
||||
}
|
33
app/application/app/Policies/UserPolicy.php
Normal file
33
app/application/app/Policies/UserPolicy.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
final readonly class UserPolicy extends Policy
|
||||
{
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('user.view');
|
||||
}
|
||||
|
||||
public function view(User $user, User $userView): bool
|
||||
{
|
||||
return $user->hasPermission('user.view');
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('user.create');
|
||||
}
|
||||
|
||||
public function update(User $user, User $userUpdate): bool
|
||||
{
|
||||
return $user->hasPermission('user.update');
|
||||
}
|
||||
|
||||
public function delete(User $user, User $userDelete): bool
|
||||
{
|
||||
return $user->hasPermission('user.delete');
|
||||
}
|
||||
}
|
71
app/application/app/Providers/AppServiceProvider.php
Normal file
71
app/application/app/Providers/AppServiceProvider.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Services\Search\Search;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->bind(CreateSearchInstanceCommand::class, function () {
|
||||
return new CreateSearchInstanceCommand(Search::class);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
if (config('app.force_https') === true) {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
$this->passwordDefaults();
|
||||
|
||||
$this->configureRateLimiting();
|
||||
Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*/
|
||||
private function configureRateLimiting(): void
|
||||
{
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
return [
|
||||
Limit::perHour(config('rate_limiting.login_max_request', 50))->by($request->getClientIp()),
|
||||
Limit::perHour(config('rate_limiting.login_max_email_request', 10))->by($request->getClientIp() . '-' . $request->input('email')),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
private function passwordDefaults(): void
|
||||
{
|
||||
Password::defaults(function () {
|
||||
$rule = Password::min(8);
|
||||
|
||||
if ($this->app->isProduction()) {
|
||||
$rule->letters()
|
||||
->mixedCase()
|
||||
->numbers()
|
||||
->symbols()
|
||||
->uncompromised();
|
||||
}
|
||||
|
||||
return $rule;
|
||||
});
|
||||
}
|
||||
}
|
59
app/application/app/Repositories/RoleRepository.php
Normal file
59
app/application/app/Repositories/RoleRepository.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
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 __construct(
|
||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||
private BuilderCommand $builderCommand
|
||||
) { }
|
||||
|
||||
public function getRoleById(int $id): ?Role
|
||||
{
|
||||
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 getRolesForSelect(array $withExcepts = []): array
|
||||
{
|
||||
return Role::query()
|
||||
->when($withExcepts, function (Builder $query, array $withExcepts) {
|
||||
$query->withTrashed()->whereNull('deleted_at')->orWhereIn('id', $withExcepts);
|
||||
})
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
61
app/application/app/Repositories/UserRepository.php
Normal file
61
app/application/app/Repositories/UserRepository.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Contracts\Search;
|
||||
use App\Models\User;
|
||||
use App\Dto\Builder\User as UserBuilderDto;
|
||||
use App\Services\User\BuilderCommand;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
final readonly class UserRepository
|
||||
{
|
||||
public function __construct(
|
||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||
private BuilderCommand $builderCommand
|
||||
) { }
|
||||
|
||||
public function getUserById(int $id): ?User
|
||||
{
|
||||
return User::query()->where('id', $id)->first();
|
||||
}
|
||||
|
||||
public function getUserByEmail(string $email): ?User
|
||||
{
|
||||
return User::query()->where('email', Str::lower($email))->first();
|
||||
}
|
||||
|
||||
public function getUsers(UserBuilderDto $userBuilderDto, array $with = []): Search
|
||||
{
|
||||
$query = $this->builderCommand->execute(
|
||||
query: User::query()->with($with),
|
||||
userBuilderDto: $userBuilderDto
|
||||
);
|
||||
|
||||
return $this->createSearchInstanceCommand->execute($query);
|
||||
}
|
||||
|
||||
public function isExistsEmail(string $email, ?int $exceptId = null): bool
|
||||
{
|
||||
return User::query()
|
||||
->where('email', Str::lower($email))
|
||||
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||
$query->where('id', '!=', $exceptId);
|
||||
})
|
||||
->withTrashed()
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function isExistsUsername(string $username, ?int $exceptId = null): bool
|
||||
{
|
||||
return User::query()
|
||||
->where('username', Str::lower($username))
|
||||
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||
$query->where('id', '!=', $exceptId);
|
||||
})
|
||||
->withTrashed()
|
||||
->exists();
|
||||
}
|
||||
}
|
55
app/application/app/Rules/Permission.php
Normal file
55
app/application/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;
|
||||
}
|
||||
}
|
||||
}
|
34
app/application/app/Rules/Repository.php
Normal file
34
app/application/app/Rules/Repository.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Closure;
|
||||
|
||||
final readonly class Repository 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;
|
||||
}
|
||||
|
||||
$validator = Validator::make([
|
||||
'slug' => $value,
|
||||
], [
|
||||
'slug' => 'min:1|max:150|regex:/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
foreach ($validator->errors()->all() as $error) {
|
||||
$fail($error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
app/application/app/Rules/Username.php
Normal file
34
app/application/app/Rules/Username.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
final readonly class Username 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;
|
||||
}
|
||||
|
||||
$validator = Validator::make([
|
||||
'username' => $value,
|
||||
], [
|
||||
'username' => 'min:1|max:70|regex:/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
foreach ($validator->errors()->all() as $error) {
|
||||
$fail($error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
app/application/app/ServiceResults/ServiceResult.php
Normal file
20
app/application/app/ServiceResults/ServiceResult.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults;
|
||||
|
||||
use App\Contracts\ServiceResult as ServiceResultContract;
|
||||
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||
|
||||
abstract class ServiceResult implements ServiceResultContract
|
||||
{
|
||||
public function isSuccess(): bool
|
||||
{
|
||||
return $this->isError() === false;
|
||||
}
|
||||
|
||||
public function isError(): bool
|
||||
{
|
||||
return $this instanceof ServiceResultErrorContract === true;
|
||||
}
|
||||
}
|
16
app/application/app/ServiceResults/ServiceResultArray.php
Normal file
16
app/application/app/ServiceResults/ServiceResultArray.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults;
|
||||
|
||||
final class ServiceResultArray extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $data,
|
||||
) { }
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
49
app/application/app/ServiceResults/ServiceResultError.php
Normal file
49
app/application/app/ServiceResults/ServiceResultError.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults;
|
||||
|
||||
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||
|
||||
final class ServiceResultError extends ServiceResult implements ServiceResultErrorContract
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $message,
|
||||
private readonly array $errors = [],
|
||||
private readonly ?int $code = null,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getCode(): ?int
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function getErrorsOrMessage(): array|string
|
||||
{
|
||||
if (!empty($this->getErrors())) {
|
||||
return $this->getErrors();
|
||||
}
|
||||
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return [
|
||||
'message' => $this->getMessage(),
|
||||
'errors' => $this->errors
|
||||
];
|
||||
}
|
||||
}
|
15
app/application/app/ServiceResults/ServiceResultSuccess.php
Normal file
15
app/application/app/ServiceResults/ServiceResultSuccess.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults;
|
||||
|
||||
final class ServiceResultSuccess extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $message
|
||||
) { }
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
23
app/application/app/ServiceResults/StoreUpdateResult.php
Normal file
23
app/application/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/application/app/Services/Admin/RoleService.php
Normal file
165
app/application/app/Services/Admin/RoleService.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Dto\Builder\Role as RoleBuilderDto;
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Dto\Service\Admin\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(),
|
||||
];
|
||||
}
|
||||
}
|
216
app/application/app/Services/Admin/UserService.php
Normal file
216
app/application/app/Services/Admin/UserService.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Dto\Builder\User as UserBuilderDto;
|
||||
use App\Dto\Service\Admin\User\StoreUpdate;
|
||||
use App\Dto\Service\User\UpdatePassword;
|
||||
use App\Helpers\Helpers;
|
||||
use App\Models\User;
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Repositories\RoleRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use App\Services\Service;
|
||||
use App\Services\User\UserCommandHandler;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class UserService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly UserCommandHandler $userCommandHandler,
|
||||
private readonly RoleRepository $roleRepository
|
||||
) { }
|
||||
|
||||
public function index(UserBuilderDto $userBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
if ($user->cannot('viewAny', User::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
$users = $this->userRepository->getUsers(
|
||||
$userBuilderDto,
|
||||
$querySettingsDto->getQueryWith()
|
||||
)->pagination(
|
||||
$querySettingsDto->getLimit(),
|
||||
$querySettingsDto->getPage()
|
||||
);
|
||||
|
||||
return $this->result([
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
if ($user->cannot('create', User::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
return $this->result([
|
||||
'user' => new User(),
|
||||
'roles' => $this->roleRepository->getRolesForSelect(),
|
||||
'userRoles' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
$modelUser = $this->userRepository->getUserById($id);
|
||||
|
||||
if (is_null($modelUser)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('view', $modelUser)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
$userRoles = $modelUser->roles()->withTrashed()->pluck('id')->toArray();
|
||||
return $this->result([
|
||||
'user' => $modelUser,
|
||||
'roles' => $this->roleRepository->getRolesForSelect($userRoles),
|
||||
'userRoles' => $userRoles,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
if ($user->cannot('create', User::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
if ($this->userRepository->isExistsEmail($data->getEmail())) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.email')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.email')])]
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->userRepository->isExistsUsername($data->getUsername())) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.username')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.username')])]
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$modelUser = DB::transaction(function () use ($data) {
|
||||
$dataUser = $this->getDataUser($data);
|
||||
|
||||
$modelUser = $this->userCommandHandler->handleStore($dataUser, $data->getPassword());
|
||||
$this->userCommandHandler->handleSyncRoles($modelUser, $data->getRoles());
|
||||
|
||||
return $modelUser;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($modelUser, __('The user was successfully created'));
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
$modelUser = $this->userRepository->getUserById($id);
|
||||
|
||||
if (is_null($modelUser)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('update', $modelUser)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
if ($this->userRepository->isExistsEmail($data->getEmail(), $modelUser->id)) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.email')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.email')])]
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->userRepository->isExistsUsername($data->getUsername(), $modelUser->id)) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.username')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.username')])]
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$modelUser = DB::transaction(function () use ($data, $modelUser) {
|
||||
$dataUser = $this->getDataUser($data);
|
||||
|
||||
$modelUser = $this->userCommandHandler->handleUpdate($modelUser, $dataUser);
|
||||
$this->userCommandHandler->handleSyncRoles($modelUser, $data->getRoles());
|
||||
|
||||
return $modelUser;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($modelUser, __('The user was successfully updated'));
|
||||
}
|
||||
|
||||
public function updatePassword(int $id, UpdatePassword $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
$modelUser = $this->userRepository->getUserById($id);
|
||||
|
||||
if (is_null($modelUser)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('update', $modelUser)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->userCommandHandler->handleUpdatePassword($modelUser, $data->getPassword());
|
||||
} catch (\Throwable $e) {
|
||||
report($e->getMessage());
|
||||
return $this->errService($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($modelUser, __('The password has been changed'));
|
||||
}
|
||||
|
||||
public function destroy(int $id, User $user): ServiceResultError | ServiceResultSuccess
|
||||
{
|
||||
$modelUser = $this->userRepository->getUserById($id);
|
||||
|
||||
if (is_null($modelUser)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('delete', $modelUser)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($modelUser) {
|
||||
$this->userCommandHandler->handleDestroy($modelUser);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->ok(__('The user has been deleted'));
|
||||
}
|
||||
|
||||
private function getDataUser(StoreUpdate $data): array
|
||||
{
|
||||
return [
|
||||
'name' => $data->getName(),
|
||||
'email' => $data->getEmail(),
|
||||
'username' => $data->getUsername(),
|
||||
'is_active' => $data->isActive(),
|
||||
];
|
||||
}
|
||||
}
|
37
app/application/app/Services/AuthService.php
Normal file
37
app/application/app/Services/AuthService.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Dto\Service\Authorization;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
final class AuthService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository
|
||||
) { }
|
||||
|
||||
public function authorization(Authorization $authorization): ServiceResultError | ServiceResultSuccess
|
||||
{
|
||||
$user = $this->userRepository->getUserByEmail($authorization->getEmail());
|
||||
if (is_null($user)) {
|
||||
return $this->errUnauthorized(__('auth.failed'));
|
||||
}
|
||||
if (Hash::check($authorization->getPassword(), $user->password) !== true) {
|
||||
return $this->errUnauthorized(__('auth.password'));
|
||||
}
|
||||
|
||||
try {
|
||||
Auth::login($user, $authorization->getRemember());
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->ok(__('auth.success'));
|
||||
}
|
||||
}
|
15
app/application/app/Services/Role/BuilderCommand.php
Normal file
15
app/application/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;
|
||||
|
||||
final readonly class BuilderCommand
|
||||
{
|
||||
public function execute(Relation | Builder $query, RoleBuilderDto $roleBuilderDto): Relation | Builder
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
}
|
26
app/application/app/Services/Role/RoleCommandHandler.php
Normal file
26
app/application/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();
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Role;
|
||||
|
||||
use App\Enums\Permission;
|
||||
use App\Exceptions\Services\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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
79
app/application/app/Services/Search/Search.php
Normal file
79
app/application/app/Services/Search/Search.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Search;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
74
app/application/app/Services/Service.php
Normal file
74
app/application/app/Services/Service.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\Site\PagePossibleWithoutTranslation;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
abstract class Service
|
||||
{
|
||||
final protected function errValidate(string $message, array $errors = []): ServiceResultError
|
||||
{
|
||||
return $this->error(Response::HTTP_UNPROCESSABLE_ENTITY, $message, $errors);
|
||||
}
|
||||
|
||||
final protected function errFobidden(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(Response::HTTP_FORBIDDEN, $message);
|
||||
}
|
||||
|
||||
final protected function errNotFound(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(Response::HTTP_NOT_FOUND, $message);
|
||||
}
|
||||
|
||||
final protected function errService(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(Response::HTTP_INTERNAL_SERVER_ERROR, $message);
|
||||
}
|
||||
|
||||
final protected function notAcceptable(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(Response::HTTP_NOT_ACCEPTABLE, $message);
|
||||
}
|
||||
|
||||
final protected function errUnauthorized(string $message): ServiceResultError
|
||||
{
|
||||
return $this->error(Response::HTTP_UNAUTHORIZED, $message);
|
||||
}
|
||||
|
||||
final protected function ok(string $message = 'OK'): ServiceResultSuccess
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
final protected function resultSitePage(Project $project, WebsiteTranslations $websiteTranslations, array $data, bool $isTranslation): PagePossibleWithoutTranslation
|
||||
{
|
||||
return new PagePossibleWithoutTranslation($project, $websiteTranslations, $data, $isTranslation);
|
||||
}
|
||||
|
||||
final protected function error(int $code, string $message, array $errors = []): ServiceResultError
|
||||
{
|
||||
return new ServiceResultError(
|
||||
message: $message,
|
||||
errors: $errors,
|
||||
code: $code
|
||||
);
|
||||
}
|
||||
}
|
59
app/application/app/Services/Site/ProfileService.php
Normal file
59
app/application/app/Services/Site/ProfileService.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Site;
|
||||
|
||||
use App\Dto\Service\Site\Profile\Update;
|
||||
use App\Dto\Service\Site\Profile\UpdateSettings;
|
||||
use App\Dto\Service\User\UpdatePassword;
|
||||
use App\Models\User;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\Services\Service;
|
||||
use App\Services\User\UserCommandHandler;
|
||||
|
||||
final class ProfileService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserCommandHandler $userCommandHandler
|
||||
) { }
|
||||
|
||||
public function update(Update $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||
{
|
||||
try {
|
||||
$data = [
|
||||
'name' => $update->getName()
|
||||
];
|
||||
$this->userCommandHandler->handleUpdate($user, $data);
|
||||
} catch (\Throwable $e) {
|
||||
report($e->getMessage());
|
||||
return $this->errService($e->getMessage());
|
||||
}
|
||||
return $this->ok(__('Profile saved successfully'));
|
||||
}
|
||||
|
||||
public function updatePassword(UpdatePassword $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||
{
|
||||
try {
|
||||
$this->userCommandHandler->handleUpdatePassword($user, $update->getPassword());
|
||||
} catch (\Throwable $e) {
|
||||
report($e->getMessage());
|
||||
return $this->errService($e->getMessage());
|
||||
}
|
||||
return $this->ok(__('The password has been changed'));
|
||||
}
|
||||
|
||||
public function updateSettings(UpdateSettings $update, User $user): ServiceResultError | ServiceResultSuccess
|
||||
{
|
||||
try {
|
||||
$data = [
|
||||
'lang' => $update->getLang(),
|
||||
'timezone' => $update->getTimezone(),
|
||||
];
|
||||
$this->userCommandHandler->handleUpdate($user, $data);
|
||||
} catch (\Throwable $e) {
|
||||
report($e->getMessage());
|
||||
return $this->errService($e->getMessage());
|
||||
}
|
||||
return $this->ok(__('The settings have been saved'));
|
||||
}
|
||||
}
|
15
app/application/app/Services/User/BuilderCommand.php
Normal file
15
app/application/app/Services/User/BuilderCommand.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\User;
|
||||
|
||||
use App\Dto\Builder\User as UserBuilderDto;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
final readonly class BuilderCommand
|
||||
{
|
||||
public function execute(Relation | Builder $query, UserBuilderDto $userBuilderDto): Relation | Builder
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
}
|
78
app/application/app/Services/User/UserCommandHandler.php
Normal file
78
app/application/app/Services/User/UserCommandHandler.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\User;
|
||||
|
||||
use App\Dto\Service\User\ManyRoleDto;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final readonly class UserCommandHandler
|
||||
{
|
||||
public function handleStore(array $data, int|string $password): User
|
||||
{
|
||||
$data['email'] = Str::lower($data['email']);
|
||||
$data['username'] = Str::lower($data['username']);
|
||||
$data['password'] = $this->hashPassword($password);
|
||||
$user = User::create($data);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function handleUpdate(User $user, array $data): User
|
||||
{
|
||||
if (isset($data['email'])) {
|
||||
$data['email'] = Str::lower($data['email']);
|
||||
}
|
||||
|
||||
if (isset($data['username'])) {
|
||||
$data['username'] = Str::lower($data['username']);
|
||||
}
|
||||
|
||||
if (isset($data['password'])) {
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
$user->update($data);
|
||||
$user->touch();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function handleConfirmationByEmail(User $user): void
|
||||
{
|
||||
$user->update(['email_verified_at' => new Carbon('NOW')]);
|
||||
}
|
||||
|
||||
public function handleUpdatePassword(User $user, int|string $password): void
|
||||
{
|
||||
$user->update(['password' => $this->hashPassword($password)]);
|
||||
}
|
||||
|
||||
public function handleSyncRoles(User $user, ManyRoleDto $roles): void
|
||||
{
|
||||
$user->roles()->sync($roles->toArray());
|
||||
}
|
||||
|
||||
public function attachRole(User $user, Role $role): void
|
||||
{
|
||||
$user->roles()->attach($role);
|
||||
}
|
||||
|
||||
public function detachRole(User $user, Role $role): void
|
||||
{
|
||||
$user->roles()->detach($user);
|
||||
}
|
||||
|
||||
private function hashPassword(int|string $password): string
|
||||
{
|
||||
return Hash::make($password);
|
||||
}
|
||||
|
||||
public function handleDestroy(User $user): void
|
||||
{
|
||||
$user->delete();
|
||||
}
|
||||
}
|
17
app/application/app/View/Components/Layout/Admin.php
Normal file
17
app/application/app/View/Components/Layout/Admin.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Layout;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Admin extends Component
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('layout.admin');
|
||||
}
|
||||
}
|
18
app/application/app/View/Components/Layout/Site.php
Normal file
18
app/application/app/View/Components/Layout/Site.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Layout;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Site extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('layout.site');
|
||||
}
|
||||
}
|
57
app/application/app/View/Components/Site/Forms/Checkbox.php
Normal file
57
app/application/app/View/Components/Site/Forms/Checkbox.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Site\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Checkbox extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly string $checkboxValue,
|
||||
private readonly ?string $userValue = '',
|
||||
private readonly ?string $notCheckedValue = null
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getCheckboxValue(): string
|
||||
{
|
||||
return (string) $this->checkboxValue;
|
||||
}
|
||||
|
||||
private function getUserValue(): string
|
||||
{
|
||||
return (string) old($this->getRequestName(), $this->userValue);
|
||||
}
|
||||
|
||||
private function getNotCheckedValue(): ?string
|
||||
{
|
||||
return $this->notCheckedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.site.forms.checkbox', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'checkboxValue' => $this->getCheckboxValue(),
|
||||
'userValue' => $this->getUserValue(),
|
||||
'notCheckedValue' => $this->getNotCheckedValue(),
|
||||
]);
|
||||
}
|
||||
}
|
26
app/application/app/View/Components/Site/Forms/Form.php
Normal file
26
app/application/app/View/Components/Site/Forms/Form.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Site\Forms;
|
||||
|
||||
use App\Helpers\Helpers;
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
abstract class Form extends Component
|
||||
{
|
||||
private ?string $requestName = null;
|
||||
|
||||
abstract protected function getName(): string;
|
||||
abstract public function render(): View;
|
||||
|
||||
protected function getRequestName(): string
|
||||
{
|
||||
if (!is_null($this->requestName)) {
|
||||
return $this->requestName;
|
||||
}
|
||||
|
||||
$this->requestName = Helpers::formatAttributeNameToRequestName($this->getName());
|
||||
|
||||
return $this->requestName;
|
||||
}
|
||||
}
|
50
app/application/app/View/Components/Site/Forms/Input.php
Normal file
50
app/application/app/View/Components/Site/Forms/Input.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Site\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Input extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly string $type = 'text',
|
||||
private readonly ?string $value = '',
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
private function getValue(): string
|
||||
{
|
||||
return (string) old($this->getRequestName(), $this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.site.forms.input', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'type' => $this->getType(),
|
||||
'value' => $this->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
54
app/application/app/View/Components/Site/Forms/Select.php
Normal file
54
app/application/app/View/Components/Site/Forms/Select.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Site\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Select extends Form
|
||||
{
|
||||
/**
|
||||
* @param string $title
|
||||
* @param string $name
|
||||
* @param array $list = [ [key => value], ... ]
|
||||
* @param null|string $value
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly array $list,
|
||||
private readonly ?string $value = ''
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getValue(): string
|
||||
{
|
||||
return (string) old($this->getRequestName(), $this->value);
|
||||
}
|
||||
|
||||
private function getList(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.site.forms.select', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'list' => $this->getList(),
|
||||
'value' => $this->getValue()
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
57
app/application/app/View/Components/Volt/Forms/Checkbox.php
Normal file
57
app/application/app/View/Components/Volt/Forms/Checkbox.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Checkbox extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly string $checkboxValue,
|
||||
private readonly ?string $userValue = '',
|
||||
private readonly ?string $notCheckedValue = null
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getCheckboxValue(): string
|
||||
{
|
||||
return (string) $this->checkboxValue;
|
||||
}
|
||||
|
||||
private function getUserValue(): string
|
||||
{
|
||||
return (string) old($this->getRequestName(), $this->userValue);
|
||||
}
|
||||
|
||||
private function getNotCheckedValue(): ?string
|
||||
{
|
||||
return $this->notCheckedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.checkbox', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'checkboxValue' => $this->getCheckboxValue(),
|
||||
'userValue' => $this->getUserValue(),
|
||||
'notCheckedValue' => $this->getNotCheckedValue(),
|
||||
]);
|
||||
}
|
||||
}
|
26
app/application/app/View/Components/Volt/Forms/Form.php
Normal file
26
app/application/app/View/Components/Volt/Forms/Form.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
|
||||
use App\Helpers\Helpers;
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
abstract class Form extends Component
|
||||
{
|
||||
private ?string $requestName = null;
|
||||
|
||||
abstract protected function getName(): string;
|
||||
abstract public function render(): View;
|
||||
|
||||
protected function getRequestName(): string
|
||||
{
|
||||
if (!is_null($this->requestName)) {
|
||||
return $this->requestName;
|
||||
}
|
||||
|
||||
$this->requestName = Helpers::formatAttributeNameToRequestName($this->getName());
|
||||
|
||||
return $this->requestName;
|
||||
}
|
||||
}
|
64
app/application/app/View/Components/Volt/Forms/Input.php
Normal file
64
app/application/app/View/Components/Volt/Forms/Input.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Input extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly string $type = 'text',
|
||||
private readonly ?string $value = '',
|
||||
private readonly ?string $example = null,
|
||||
private readonly ?string $allowedCharacters = null,
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
private function getValue(): string
|
||||
{
|
||||
return (string) old($this->getRequestName(), $this->value);
|
||||
}
|
||||
|
||||
private function getExample(): ?string
|
||||
{
|
||||
return $this->example;
|
||||
}
|
||||
|
||||
public function getAllowedCharacters(): ?string
|
||||
{
|
||||
return $this->allowedCharacters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.input', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'type' => $this->getType(),
|
||||
'value' => $this->getValue(),
|
||||
'example' => $this->getExample(),
|
||||
'allowedCharacters' => $this->getAllowedCharacters(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class MultiCheckbox extends Form
|
||||
{
|
||||
/**
|
||||
* @param string $title
|
||||
* @param string $name
|
||||
* @param array $list = [ [key => value], ... ]
|
||||
* @param array $value
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly array $list,
|
||||
private readonly array $value = []
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getValue(): array
|
||||
{
|
||||
return old($this->getRequestName(), $this->value);
|
||||
}
|
||||
|
||||
private function getList(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.multi_checkbox', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'list' => $this->getList(),
|
||||
'value' => $this->getValue()
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\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;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getValue(): Collection
|
||||
{
|
||||
$value = old($this->getRequestName(), $this->value);
|
||||
return collect($value);
|
||||
}
|
||||
|
||||
private function getRole(): Role
|
||||
{
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.permissions_for_role', [
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'permissions' => Permission::cases(),
|
||||
'role' => $this->getRole(),
|
||||
'value' => $this->getValue(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
15
app/application/artisan
Executable file
15
app/application/artisan
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
$status = (require_once __DIR__.'/bootstrap/app.php')
|
||||
->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
26
app/application/bootstrap/app.php
Normal file
26
app/application/bootstrap/app.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
using: function () {
|
||||
Route::middleware(['web', \App\Http\Middleware\UserLocale::class])
|
||||
->group(base_path('routes/web.php'));
|
||||
|
||||
Route::middleware(['web', 'auth', 'verified', \App\Http\Middleware\UserIsActive::class, \App\Http\Middleware\AdminPanel::class, \App\Http\Middleware\UserLocale::class])
|
||||
->prefix('admin')
|
||||
->as('admin.')
|
||||
->group(base_path('routes/admin.php'));
|
||||
},
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
//
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
//
|
||||
})->create();
|
2
app/application/bootstrap/cache/.gitignore
vendored
Executable file
2
app/application/bootstrap/cache/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user