Moved from "app/src" to "app/application".
Otherwise phpstorm doesn't understand the paths correctly. He thinks that this is not a complete application, but a package. And when creating a class, the namespace indicates “app” with a small letter, but should be “App”.
This commit is contained in:
94
app/application/app/Console/Commands/CreateUserAdmin.php
Normal file
94
app/application/app/Console/Commands/CreateUserAdmin.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Dto\User\ManyRoleDto;
|
||||
use App\Enums\SystemRole;
|
||||
use App\Repositories\RoleRepository;
|
||||
use App\Services\User\UserCommandHandler;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
final class CreateUserAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:create-user-admin {email} {password}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create admin user.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(UserCommandHandler $userCommandHandler, RoleRepository $roleRepository): void
|
||||
{
|
||||
$validator = $this->getData();
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->errorMessageAndStop($validator->errors()->all());
|
||||
}
|
||||
$data = $validator->valid();
|
||||
|
||||
try {
|
||||
$role = $roleRepository->getRoleByCode(SystemRole::Admin->value);
|
||||
if (is_null($role)) {
|
||||
$this->errorMessageAndStop('Administrator role not found.');
|
||||
}
|
||||
$user = DB::transaction(function () use($data, $userCommandHandler, $role) {
|
||||
$data['name'] = 'Administrator';
|
||||
$user = $userCommandHandler->handleStore($data, $data['password']);
|
||||
$userCommandHandler->handleConfirmationByEmail($user);
|
||||
$roles = new ManyRoleDto([$role->id]);
|
||||
$userCommandHandler->handleSyncRoles($user, $roles);
|
||||
|
||||
return $user;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessageAndStop($e->getMessage());
|
||||
}
|
||||
|
||||
$this->info('The command was successful!');
|
||||
}
|
||||
|
||||
private function getData(): Validator
|
||||
{
|
||||
return ValidatorFacade::make([
|
||||
'email' => $this->argument('email'),
|
||||
'password' => $this->argument('password'),
|
||||
], [
|
||||
'email' => ['required', 'email', 'unique:users,email'],
|
||||
'password' => ['required', Password::default()],
|
||||
]);
|
||||
}
|
||||
|
||||
private function stop(): never
|
||||
{
|
||||
exit;
|
||||
}
|
||||
|
||||
private function errorMessageAndStop(string | array $error): never
|
||||
{
|
||||
$this->info('User not created. See error messages below:');
|
||||
|
||||
if (is_array($error)) {
|
||||
foreach ($error as $err) {
|
||||
$this->error($err);
|
||||
}
|
||||
} else {
|
||||
$this->error($error);
|
||||
}
|
||||
|
||||
$this->stop();
|
||||
}
|
||||
}
|
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;
|
||||
}
|
15
app/application/app/Contracts/Models/Storage.php
Normal file
15
app/application/app/Contracts/Models/Storage.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\Models;
|
||||
|
||||
use App\Enums\StorageType;
|
||||
use App\Models\Storage as StorageModel;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface Storage
|
||||
{
|
||||
public function storage(): MorphMany;
|
||||
public function getStorageOne(StorageType $type): ?StorageModel;
|
||||
public function getStorageMany(StorageType $type): Collection;
|
||||
}
|
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;
|
||||
}
|
10
app/application/app/Contracts/ServiceResult.php
Normal file
10
app/application/app/Contracts/ServiceResult.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?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;
|
||||
}
|
11
app/application/app/Contracts/StorageType.php
Normal file
11
app/application/app/Contracts/StorageType.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface StorageType
|
||||
{
|
||||
public function getTitle(): string;
|
||||
public function getAcceptMimes(): array;
|
||||
public function getFolderName(): string;
|
||||
public static function cases(): array;
|
||||
}
|
10
app/application/app/Contracts/StorageType/Audio.php
Normal file
10
app/application/app/Contracts/StorageType/Audio.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\StorageType;
|
||||
|
||||
use App\Contracts\StorageType as StorageTypeContract;
|
||||
|
||||
interface Audio extends StorageTypeContract
|
||||
{
|
||||
public function isAudio(): bool;
|
||||
}
|
10
app/application/app/Contracts/StorageType/Image.php
Normal file
10
app/application/app/Contracts/StorageType/Image.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\StorageType;
|
||||
|
||||
use App\Contracts\StorageType as StorageTypeContract;
|
||||
|
||||
interface Image extends StorageTypeContract
|
||||
{
|
||||
public function isImage(): bool;
|
||||
}
|
10
app/application/app/Contracts/StorageType/Video.php
Normal file
10
app/application/app/Contracts/StorageType/Video.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts\StorageType;
|
||||
|
||||
use App\Contracts\StorageType as StorageTypeContract;
|
||||
|
||||
interface Video extends StorageTypeContract
|
||||
{
|
||||
public function isVideo(): bool;
|
||||
}
|
10
app/application/app/Dto/Builder/Project.php
Normal file
10
app/application/app/Dto/Builder/Project.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class Project
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Language;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
|
||||
final readonly class NewLanguage extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private int $index,
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getIndex(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
}
|
21
app/application/app/Dto/Service/Admin/Project/Index.php
Normal file
21
app/application/app/Dto/Service/Admin/Project/Index.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Project;
|
||||
|
||||
use App\Dto\Builder\Project;
|
||||
use App\Dto\Service\Pages;
|
||||
|
||||
final readonly class Index extends Pages
|
||||
{
|
||||
public function __construct(
|
||||
private Project $projectBuilderDto,
|
||||
int $page
|
||||
) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
|
||||
public function getProjectBuilderDto(): Project
|
||||
{
|
||||
return $this->projectBuilderDto;
|
||||
}
|
||||
}
|
41
app/application/app/Dto/Service/Admin/Project/Language.php
Normal file
41
app/application/app/Dto/Service/Admin/Project/Language.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Project;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
|
||||
final readonly class Language extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $title,
|
||||
private string $code,
|
||||
private int $sort,
|
||||
private bool $isDefault,
|
||||
private ?int $id,
|
||||
) { }
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getCode(): string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getSort(): int
|
||||
{
|
||||
return $this->sort;
|
||||
}
|
||||
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return $this->isDefault;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
18
app/application/app/Dto/Service/Admin/Project/Languages.php
Normal file
18
app/application/app/Dto/Service/Admin/Project/Languages.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Project;
|
||||
|
||||
final class Languages
|
||||
{
|
||||
private $languages = [];
|
||||
|
||||
public function addLanguage(Language $language): void
|
||||
{
|
||||
$this->languages[] = $language;
|
||||
}
|
||||
|
||||
public function getLanguages(): array
|
||||
{
|
||||
return $this->languages;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Project;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Dto\Service\Storage\Storages;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $code,
|
||||
private bool $isPublic,
|
||||
private Languages $languages,
|
||||
private Storages $storages,
|
||||
private ?string $httpHost,
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getCode(): string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return $this->isPublic;
|
||||
}
|
||||
|
||||
public function getLanguages(): Languages
|
||||
{
|
||||
return $this->languages;
|
||||
}
|
||||
|
||||
public function getHttpHost(): ?string
|
||||
{
|
||||
return $this->httpHost;
|
||||
}
|
||||
|
||||
public function getStorages(): Storages
|
||||
{
|
||||
return $this->storages;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
36
app/application/app/Dto/Service/Admin/User/StoreUpdate.php
Normal file
36
app/application/app/Dto/Service/Admin/User/StoreUpdate.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\User;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Dto\User\ManyRoleDto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $email,
|
||||
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;
|
||||
}
|
||||
}
|
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/Private/Profile/Update.php
Normal file
17
app/application/app/Dto/Service/Private/Profile/Update.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Private\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\Private\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;
|
||||
}
|
||||
}
|
29
app/application/app/Dto/Service/Storage/File.php
Normal file
29
app/application/app/Dto/Service/Storage/File.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Storage;
|
||||
|
||||
use App\Contracts\StorageType;
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Exceptions\Dto\Storage\FileException;
|
||||
|
||||
final readonly class File extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private StorageType $storageType,
|
||||
) {
|
||||
if ($this->id < 1) {
|
||||
throw new FileException('ID cannot be equal to or less than zero: ' . $this->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getStorageType(): StorageType
|
||||
{
|
||||
return $this->storageType;
|
||||
}
|
||||
}
|
57
app/application/app/Dto/Service/Storage/Storage.php
Normal file
57
app/application/app/Dto/Service/Storage/Storage.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Storage;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Enums\StorageType;
|
||||
use app\Exceptions\Dto\Storage\StorageException;
|
||||
|
||||
final readonly class Storage extends Dto
|
||||
{
|
||||
private ?File $file;
|
||||
private bool $isDelete;
|
||||
|
||||
public function __construct(
|
||||
array $data,
|
||||
private StorageType $storageType,
|
||||
private bool $isMany,
|
||||
) {
|
||||
$file = null;
|
||||
if (isset($data['file'])) {
|
||||
if ((int) $data['file'] <= 0) {
|
||||
throw new StorageException('ID cannot be equal to or less than zero: ' . $data['file']);
|
||||
}
|
||||
$file = new File(
|
||||
id: (int) $data['file'],
|
||||
storageType: $this->storageType,
|
||||
);
|
||||
}
|
||||
$this->file = $file;
|
||||
$this->isDelete = !empty($data['delete']);
|
||||
}
|
||||
|
||||
public function getFile(): ?File
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function isDelete(): bool
|
||||
{
|
||||
return $this->isDelete;
|
||||
}
|
||||
|
||||
public function isFile(): bool
|
||||
{
|
||||
return \is_null($this->file) === false;
|
||||
}
|
||||
|
||||
public function getStorageType(): StorageType
|
||||
{
|
||||
return $this->storageType;
|
||||
}
|
||||
|
||||
public function isMany(): bool
|
||||
{
|
||||
return $this->isMany;
|
||||
}
|
||||
}
|
73
app/application/app/Dto/Service/Storage/Storages.php
Normal file
73
app/application/app/Dto/Service/Storage/Storages.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Storage;
|
||||
|
||||
use App\Enums\StorageType;
|
||||
use app\Exceptions\Dto\Storage\StoragesException;
|
||||
|
||||
final class Storages
|
||||
{
|
||||
private array $storages = [];
|
||||
|
||||
public function add(array $data, StorageType $type): void
|
||||
{
|
||||
if (isset($this->storages[$type->value])) {
|
||||
throw new StoragesException('You cannot attach two files of the same type!');
|
||||
}
|
||||
$this->storages[$type->value] = new Storage(data: $data, storageType: $type, isMany: false);
|
||||
}
|
||||
|
||||
public function addMany(array $data, StorageType $type): void
|
||||
{
|
||||
if (!isset($this->storages[$type->value])) {
|
||||
$this->storages[$type->value] = [];
|
||||
}
|
||||
|
||||
foreach ($data as $storageData) {
|
||||
$this->storages[$type->value][] = new Storage(data: $storageData, storageType: $type, isMany: true);
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->storages;
|
||||
}
|
||||
|
||||
public function getAllStorageIds(): array
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($this->storages as $storage) {
|
||||
/** @var Storage $storage */
|
||||
if (!is_array($storage)) {
|
||||
if ($storage->isFile() && !$storage->isDelete()) {
|
||||
$ids[] = $storage->getFile()->getId();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
foreach ($storage as $storageOne) {
|
||||
/** @var Storage $storageOne */
|
||||
if ($storageOne->isFile() && !$storageOne->isDelete()) {
|
||||
$ids[] = $storageOne->getFile()->getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public function getAllStorages(): array
|
||||
{
|
||||
$storages = [];
|
||||
foreach ($this->storages as $storage) {
|
||||
if (!is_array($storage)) {
|
||||
$storages[] = $storage;
|
||||
continue;
|
||||
}
|
||||
foreach ($storage as $storageOne) {
|
||||
$storages[] = $storageOne;
|
||||
}
|
||||
}
|
||||
|
||||
return $storages;
|
||||
}
|
||||
}
|
32
app/application/app/Dto/Service/Storage/Upload.php
Normal file
32
app/application/app/Dto/Service/Storage/Upload.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Storage;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Enums\Morph;
|
||||
use App\Enums\StorageType;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
final readonly class Upload extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private UploadedFile $file,
|
||||
private StorageType $storageType,
|
||||
private Morph $morph,
|
||||
) { }
|
||||
|
||||
public function getFile(): UploadedFile
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function getStorageType(): StorageType
|
||||
{
|
||||
return $this->storageType;
|
||||
}
|
||||
|
||||
public function getMorph(): Morph
|
||||
{
|
||||
return $this->morph;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
32
app/application/app/Dto/User/ManyRoleDto.php
Normal file
32
app/application/app/Dto/User/ManyRoleDto.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\User;
|
||||
|
||||
use App\Exceptions\Dto\User\ManyRoleDtoException;
|
||||
|
||||
final class ManyRoleDto
|
||||
{
|
||||
private array $roles = [];
|
||||
|
||||
public function __construct(array $roles = []) {
|
||||
foreach ($roles as $role) {
|
||||
if (!is_numeric($role) || is_float($role)) {
|
||||
throw new ManyRoleDtoException('Not an integer: ' . $role . '.');
|
||||
}
|
||||
$this->add((int) $role);
|
||||
}
|
||||
}
|
||||
|
||||
public function add(int $id): void
|
||||
{
|
||||
if ($id < 1) {
|
||||
throw new ManyRoleDtoException('Only Integer > 0.');
|
||||
}
|
||||
$this->roles[] = $id;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
}
|
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());
|
||||
}
|
||||
}
|
31
app/application/app/Enums/Morph.php
Normal file
31
app/application/app/Enums/Morph.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Models\Project;
|
||||
|
||||
enum Morph: int
|
||||
{
|
||||
case Project = 1;
|
||||
|
||||
public function getPathModel(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Project => Project::class,
|
||||
};
|
||||
}
|
||||
|
||||
public function getFolderName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public static function map(): array
|
||||
{
|
||||
$map = [];
|
||||
foreach (self::cases() as $item) {
|
||||
$map[$item->value] = $item->getPathModel();
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
}
|
62
app/application/app/Enums/Permission.php
Normal file
62
app/application/app/Enums/Permission.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum Permission: string
|
||||
{
|
||||
case AdminPanel = 'allow-admin-panel';
|
||||
case Role = 'role';
|
||||
case User = 'user';
|
||||
case Project = 'project';
|
||||
|
||||
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'),
|
||||
];
|
||||
}
|
||||
}
|
53
app/application/app/Enums/StorageType.php
Normal file
53
app/application/app/Enums/StorageType.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Contracts\StorageType\Audio;
|
||||
use App\Contracts\StorageType\Image;
|
||||
use App\Contracts\StorageType\Video;
|
||||
|
||||
enum StorageType: int implements Image, Video, Audio
|
||||
{
|
||||
case Logo = 1;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Logo => __('validation.attributes.logo'),
|
||||
};
|
||||
}
|
||||
|
||||
public function getAcceptMimes(): array
|
||||
{
|
||||
return match ($this) {
|
||||
self::Logo => ['jpeg', 'jpg', 'png'],
|
||||
};
|
||||
}
|
||||
|
||||
public function isImage(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::Logo => true,
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
public function isVideo(): bool
|
||||
{
|
||||
return match ($this->name) {
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
public function isAudio(): bool
|
||||
{
|
||||
return match ($this->name) {
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
public function getFolderName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
9
app/application/app/Enums/SystemRole.php
Normal file
9
app/application/app/Enums/SystemRole.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum SystemRole: string
|
||||
{
|
||||
case Admin = 'admin';
|
||||
case AdminProject = 'admin-project';
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Dto\Storage;
|
||||
|
||||
final class FileException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\Exceptions\Dto\Storage;
|
||||
|
||||
final class StorageException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\Exceptions\Dto\Storage;
|
||||
|
||||
final class StoragesException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Dto\User;
|
||||
|
||||
final class ManyRoleDtoException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\Exceptions\Services\Storage;
|
||||
|
||||
final class StorageCommandHandlerException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Services\Storage;
|
||||
|
||||
final class StorageSaveFileException extends \Exception
|
||||
{
|
||||
|
||||
}
|
40
app/application/app/Helpers/Helpers.php
Normal file
40
app/application/app/Helpers/Helpers.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User;
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
public static function getUserTimeZone() {
|
||||
return auth()->user()?->timezone ?? config('app.user_timezone');
|
||||
}
|
||||
|
||||
/**
|
||||
* $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,25 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\Languages\NewLanguageRequest;
|
||||
use App\Http\Resources\Admin\Languages\NewLanguage;
|
||||
use App\Services\Admin\LanguageService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
final class LanguagesController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LanguageService $languageService,
|
||||
) { }
|
||||
|
||||
public function newLanguage(NewLanguageRequest $request): JsonResponse
|
||||
{
|
||||
$result = $this->languageService->newLanguage($request->getDto());
|
||||
if ($result->isError()) {
|
||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||
}
|
||||
|
||||
return response()->json(new NewLanguage($result));
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Admin\Projects\IndexRequest;
|
||||
use App\Http\Requests\Admin\Projects\StoreUpdateRequest;
|
||||
use App\Services\Admin\ProjectService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class ProjectsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectService $projectService
|
||||
) { }
|
||||
|
||||
public function index(IndexRequest $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$data = $request->getDto();
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
);
|
||||
|
||||
$result = $this->projectService->index($data->getProjectBuilderDto(), $querySettingsDto, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('admin/projects/index', $result->getData());
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->projectService->create($user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('admin/projects/create', $result->getData());
|
||||
}
|
||||
|
||||
public function edit(int $id, Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->projectService->edit($id, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('admin/projects/edit', $result->getData());
|
||||
}
|
||||
|
||||
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->projectService->store($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.projects.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->projectService->update($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.projects.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function destroy(int $id, Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->projectService->destroy($id, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.projects.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
@@ -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: ['morph']
|
||||
);
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
47
app/application/app/Http/Controllers/AuthController.php
Normal file
47
app/application/app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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');
|
||||
}
|
||||
|
||||
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('admin.home');
|
||||
}
|
||||
|
||||
public function logout(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect(route('login'));
|
||||
}
|
||||
}
|
18
app/application/app/Http/Controllers/Controller.php
Normal file
18
app/application/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
final protected function errors(ServiceResultErrorContract $result): never
|
||||
{
|
||||
if ($result->getCode() === Response::HTTP_UNPROCESSABLE_ENTITY) {
|
||||
redirect()->back()->withInput()->withErrors($result->getErrors());
|
||||
exit;
|
||||
}
|
||||
abort($result->getCode(), $result->getMessage());
|
||||
}
|
||||
}
|
10
app/application/app/Http/Controllers/Private/Controller.php
Normal file
10
app/application/app/Http/Controllers/Private/Controller.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Http\Controllers\Controller as BaseController;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Enums\Lang;
|
||||
use App\Helpers\Helpers;
|
||||
use App\Http\Requests\Private\Profile\UpdatePasswordRequest;
|
||||
use App\Http\Requests\Private\Profile\UpdateRequest;
|
||||
use App\Http\Requests\Private\Profile\UpdateSettingsRequest;
|
||||
use App\Services\Private\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('private/profile/profile', [
|
||||
'user' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(Request $request): View
|
||||
{
|
||||
return view('private/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());
|
||||
}
|
||||
}
|
10
app/application/app/Http/Controllers/Storage/Controller.php
Normal file
10
app/application/app/Http/Controllers/Storage/Controller.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Storage;
|
||||
|
||||
use App\Http\Controllers\Controller as BaseController;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace App\Http\Controllers\Storage;
|
||||
|
||||
use App\Http\Requests\Storage\ImageRequest;
|
||||
use App\Http\Resources\Storage\Upload;
|
||||
use App\Services\Storage\ImageService;
|
||||
|
||||
final class ImagesController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImageService $imageService,
|
||||
) { }
|
||||
|
||||
public function uploadAndResize(ImageRequest $request)
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->imageService->uploadAndResize($data, $user);
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||
}
|
||||
|
||||
return response()->json(new Upload($result->getStorage()));
|
||||
}
|
||||
}
|
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
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,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Languages;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Admin\Language\NewLanguage;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NewLanguageRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'index' => ['required', 'numeric'],
|
||||
'name' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): NewLanguage
|
||||
{
|
||||
return new NewLanguage(
|
||||
name: $this->input('name'),
|
||||
index: $this->input('index'),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Projects;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Builder\Project;
|
||||
use App\Dto\Service\Admin\Project\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.projects.index');
|
||||
return [
|
||||
'page' => ['nullable', 'numeric', 'min:1']
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Index
|
||||
{
|
||||
return new Index(
|
||||
projectBuilderDto: new Project(),
|
||||
page: (int) $this->input('page', 1)
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Projects;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Admin\Project\Language;
|
||||
use App\Dto\Service\Admin\Project\Languages;
|
||||
use App\Dto\Service\Admin\Project\StoreUpdate;
|
||||
use App\Dto\Service\Storage\Storages;
|
||||
use App\Enums\StorageType;
|
||||
use App\Rules\HttpHost;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'code' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-z0-9_-]+$/i'],
|
||||
'http_host' => ['nullable', 'string', 'max:255', new HttpHost()],
|
||||
'is_public' => ['required', 'boolean'],
|
||||
|
||||
'logo.file' => ['nullable', 'numeric', 'min:1'],
|
||||
'logo.delete' => ['nullable', 'boolean'],
|
||||
|
||||
'languages.items.*.id' => ['nullable', 'numeric'],
|
||||
'languages.items.*.title' => ['required', 'string', 'max:255'],
|
||||
'languages.items.*.code' => ['required', 'string', 'min:2', 'max:30', 'regex:/^[a-zA-Z_]+$/i'],
|
||||
'languages.items.*.sort' => ['required', 'numeric', 'min:-1000', 'max:1000'],
|
||||
'languages.default' => ['required', 'numeric', function (string $attribute, mixed $value, \Closure $fail) {
|
||||
$languages = $this->input('languages.items', []);
|
||||
if (!isset($languages[$value])) {
|
||||
$this->validator->getMessageBag()->add('languages.default', __('validation.required', ['attribute' => __('validation.attributes.language-default')]));
|
||||
}
|
||||
}],
|
||||
'languages.items' => ['required', 'array', function (string $attribute, mixed $value, \Closure $fail) {
|
||||
$values = [];
|
||||
foreach ($value as $index => $item) {
|
||||
if (!isset($item['code'])) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($item['code'], $values)) {
|
||||
$this->validator->getMessageBag()->add('languages.items.' . $index . '.code', __('validation.unique', ['attribute' => __('validation.attributes.language-code')]));
|
||||
}
|
||||
$values[] = $item['code'];
|
||||
}
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function getDto(): StoreUpdate
|
||||
{
|
||||
return new StoreUpdate(
|
||||
name: $this->input('name'),
|
||||
code: $this->input('code'),
|
||||
isPublic: (bool) $this->input('is_public', false),
|
||||
languages: $this->languages(),
|
||||
storages: $this->storages(),
|
||||
httpHost: $this->input('http_host', null),
|
||||
);
|
||||
}
|
||||
|
||||
private function languages(): Languages
|
||||
{
|
||||
$languages = new Languages();
|
||||
|
||||
$default = $this->input('languages.default', null);
|
||||
if ($default !== null) {
|
||||
$default = (int) $default;
|
||||
}
|
||||
|
||||
foreach ($this->input('languages.items', []) as $index => $lang) {
|
||||
$languageId = $lang['id'] ?? null;
|
||||
if ($languageId !== null) {
|
||||
$languageId = (int) $languageId;
|
||||
}
|
||||
$language = new Language(
|
||||
title: $lang['title'],
|
||||
code: $lang['code'],
|
||||
sort: (int) $lang['sort'],
|
||||
isDefault: ($default === $index),
|
||||
id: $languageId,
|
||||
);
|
||||
$languages->addLanguage($language);
|
||||
}
|
||||
|
||||
return $languages;
|
||||
}
|
||||
|
||||
private function storages(): Storages
|
||||
{
|
||||
$storages = new Storages();
|
||||
|
||||
$logo = $this->get('logo', []);
|
||||
$storages->add($logo, StorageType::Logo);
|
||||
|
||||
return $storages;
|
||||
}
|
||||
}
|
@@ -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,42 @@
|
||||
<?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\User\ManyRoleDto;
|
||||
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'],
|
||||
'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'),
|
||||
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'));
|
||||
}
|
||||
}
|
32
app/application/app/Http/Requests/AuthorizationRequest.php
Normal file
32
app/application/app/Http/Requests/AuthorizationRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'email', 'max:255'],
|
||||
'password' => ['required', 'min:3'],
|
||||
'captcha-verified' => ['captcha'],
|
||||
'remember' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
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\Private\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\Private\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Private\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\Private\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Private\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),
|
||||
);
|
||||
}
|
||||
}
|
40
app/application/app/Http/Requests/Storage/ImageRequest.php
Normal file
40
app/application/app/Http/Requests/Storage/ImageRequest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Storage;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Storage\Upload;
|
||||
use App\Enums\Morph;
|
||||
use App\Enums\StorageType;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
final class ImageRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$mimes = '';
|
||||
if ($this->has('storage_type')) {
|
||||
$storageType = StorageType::tryFrom((int) $this->input('storage_type')) ?? [];
|
||||
$mimes = implode(',', $storageType->getAcceptMimes());
|
||||
}
|
||||
|
||||
return [
|
||||
'file' => ['required', 'file', 'mimes:' . $mimes],
|
||||
'storage_type' => ['required', 'numeric', new Enum(StorageType::class)],
|
||||
'morph' => ['required', 'numeric', new Enum(Morph::class)],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Upload
|
||||
{
|
||||
return new Upload(
|
||||
file: $this->file('file'),
|
||||
storageType: StorageType::from((int) $this->input('storage_type')),
|
||||
morph: Morph::from((int) $this->input('morph')),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Admin\Languages;
|
||||
|
||||
use App\ServiceResults\Admin\LanguageService\NewLanguageResult;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class NewLanguage extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @var NewLanguageResult
|
||||
*/
|
||||
public $resource;
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'html' => view('components.volt.forms.languages.language', [
|
||||
'index' => $this->resource->getIndex(),
|
||||
'name' => $this->resource->getName(),
|
||||
'lang' => $this->resource->getLanguage()->toArray(),
|
||||
])->render(),
|
||||
];
|
||||
}
|
||||
}
|
23
app/application/app/Http/Resources/Storage/Upload.php
Normal file
23
app/application/app/Http/Resources/Storage/Upload.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Storage;
|
||||
|
||||
use App\Models\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class Upload extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @var Storage
|
||||
*/
|
||||
public $resource;
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->resource->id,
|
||||
'url' => $this->resource->url,
|
||||
];
|
||||
}
|
||||
}
|
50
app/application/app/Models/Project.php
Normal file
50
app/application/app/Models/Project.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\StorageTrait;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Contracts\Models\Storage as StorageContract;
|
||||
|
||||
final class Project extends Model implements StorageContract
|
||||
{
|
||||
use HasFactory, SoftDeletes, StorageTrait;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
'http_host',
|
||||
'is_public'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_public' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function roles(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Role::class, 'morph');
|
||||
}
|
||||
|
||||
public function languages(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProjectLanguage::class);
|
||||
}
|
||||
}
|
50
app/application/app/Models/ProjectLanguage.php
Normal file
50
app/application/app/Models/ProjectLanguage.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Scopes\SortScope;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
|
||||
|
||||
#[ScopedBy([SortScope::class])]
|
||||
final class ProjectLanguage extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The model's default values for attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'is_default' => false,
|
||||
'sort' => 100,
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'code',
|
||||
'is_default',
|
||||
'sort',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_default' => 'boolean',
|
||||
'sort' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
75
app/application/app/Models/Role.php
Normal file
75
app/application/app/Models/Role.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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\Relations\MorphTo;
|
||||
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');
|
||||
}
|
||||
|
||||
protected function nameWithMorph(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$name = $this->name;
|
||||
if ($this->morph_type) {
|
||||
$name .= ' (' . __('admin-sections.Projects') . ': ' . $this->morph?->name . ')';
|
||||
}
|
||||
|
||||
return $name;
|
||||
},
|
||||
)->shouldCache();
|
||||
}
|
||||
|
||||
public function morph(): MorphTo
|
||||
{
|
||||
return $this->morphTo('morph');
|
||||
}
|
||||
|
||||
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 () => ( \is_null($this->morphable_type) && \is_null($this->morphable_id) && $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] ?? '';
|
||||
}
|
||||
}
|
18
app/application/app/Models/Scopes/SortScope.php
Normal file
18
app/application/app/Models/Scopes/SortScope.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Scopes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
final class SortScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
$builder->orderBy('sort', 'asc');
|
||||
}
|
||||
}
|
70
app/application/app/Models/Storage.php
Normal file
70
app/application/app/Models/Storage.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\StorageType;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage as StorageSupport;
|
||||
|
||||
class Storage extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'storage';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'data',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'data' => 'array',
|
||||
'type' => StorageType::class,
|
||||
];
|
||||
}
|
||||
|
||||
protected function dataImages(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->data['images'] ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
protected function url(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (\is_null($this->file)) {
|
||||
return '';
|
||||
}
|
||||
return StorageSupport::disk(config('storage.disk'))->url($this->file) . '?time=' . $this->updated_at->getTimestamp();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected function path(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (\is_null($this->file)) {
|
||||
return '';
|
||||
}
|
||||
return StorageSupport::disk(config('storage.disk'))->path($this->file);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
26
app/application/app/Models/Traits/StorageTrait.php
Normal file
26
app/application/app/Models/Traits/StorageTrait.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Enums\StorageType;
|
||||
use App\Models\Storage as StorageModel;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
trait StorageTrait
|
||||
{
|
||||
public function storage(): MorphMany
|
||||
{
|
||||
return $this->morphMany(StorageModel::class, 'morph');
|
||||
}
|
||||
|
||||
public function getStorageOne(StorageType $type): ?StorageModel
|
||||
{
|
||||
return $this->storage->firstWhere('type', $type);
|
||||
}
|
||||
|
||||
public function getStorageMany(StorageType $type): Collection
|
||||
{
|
||||
return $this->storage->where('type', $type);
|
||||
}
|
||||
}
|
87
app/application/app/Models/User.php
Normal file
87
app/application/app/Models/User.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
final class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'nickname',
|
||||
'timezone',
|
||||
'lang',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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,
|
||||
];
|
||||
}
|
||||
|
||||
public function roles(): belongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Role::class);
|
||||
}
|
||||
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
return $this->permissions->search($permission) !== false;
|
||||
}
|
||||
|
||||
protected function isAdmin(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->roles
|
||||
->where('code', SystemRole::Admin->value)
|
||||
->whereNull('morphable_type')
|
||||
->whereNull('morphable_id')
|
||||
->isNotEmpty(),
|
||||
)->shouldCache();
|
||||
}
|
||||
|
||||
protected function permissions(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$roles = $this->roles->modelKeys();
|
||||
return RolePermission::query()->whereIn('role_id', $roles)->select('permission')->pluck('permission');
|
||||
},
|
||||
)->shouldCache();
|
||||
}
|
||||
}
|
16
app/application/app/Policies/AdminPanel.php
Normal file
16
app/application/app/Policies/AdminPanel.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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')
|
||||
|| $user->roles->where('code', SystemRole::AdminProject->value)->isNotEmpty();
|
||||
}
|
||||
}
|
20
app/application/app/Policies/Policy.php
Normal file
20
app/application/app/Policies/Policy.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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_admin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
43
app/application/app/Policies/ProjectPolicy.php
Normal file
43
app/application/app/Policies/ProjectPolicy.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
|
||||
final readonly class ProjectPolicy extends Policy
|
||||
{
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('project.view');
|
||||
}
|
||||
|
||||
public function view(User $user, Project $project): bool
|
||||
{
|
||||
return $user->hasPermission('project.view');
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->hasPermission('project.create');
|
||||
}
|
||||
|
||||
public function update(User $user, Project $project): bool
|
||||
{
|
||||
return $user->hasPermission('project.update');
|
||||
}
|
||||
|
||||
public function delete(User $user, Project $project): bool
|
||||
{
|
||||
return $user->hasPermission('project.delete');
|
||||
}
|
||||
|
||||
public function upload(User $user): bool
|
||||
{
|
||||
if ($user->hasPermission('project.create') || $user->hasPermission('project.update')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
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');
|
||||
}
|
||||
}
|
92
app/application/app/Providers/AppServiceProvider.php
Normal file
92
app/application/app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Enums\Morph;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Services\Search\Search;
|
||||
use App\Services\Storage\Image\ResizeCommandHandler;
|
||||
use App\Services\Storage\ImageService;
|
||||
use App\Services\Storage\StorageCommandHandler;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
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);
|
||||
});
|
||||
|
||||
$this->app->bind(StorageCommandHandler::class, function () {
|
||||
return new StorageCommandHandler(disc: (string) config('storage.disk'));
|
||||
});
|
||||
|
||||
$this->app->bind(ImageService::class, function (Application $app) {
|
||||
return new ImageService(
|
||||
storageCommandHandler: $app->make(StorageCommandHandler::class),
|
||||
resizeCommandHandler: $app->make(ResizeCommandHandler::class),
|
||||
maxImageWidth: (int) config('storage.max_image_width', 4000),
|
||||
maxImageHeight: (int) config('storage.max_image_height', 4000),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
if (config('app.force_https') === true) {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
$this->passwordDefaults();
|
||||
|
||||
Relation::enforceMorphMap(Morph::map());
|
||||
|
||||
$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;
|
||||
});
|
||||
}
|
||||
}
|
49
app/application/app/Repositories/ProjectRepository.php
Normal file
49
app/application/app/Repositories/ProjectRepository.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Contracts\Search;
|
||||
use App\Dto\Builder\Project as ProjectBuilderDto;
|
||||
use App\Models\Project;
|
||||
use App\Services\Project\BuilderCommand;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
final readonly class ProjectRepository
|
||||
{
|
||||
public function __construct(
|
||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||
private BuilderCommand $builderCommand
|
||||
) { }
|
||||
|
||||
public function getProjects(ProjectBuilderDto $projectBuilderDto, array $with = []): Search
|
||||
{
|
||||
$query = $this->builderCommand->execute(
|
||||
query: Project::query()->with($with),
|
||||
projectBuilderDto: $projectBuilderDto
|
||||
);
|
||||
|
||||
return $this->createSearchInstanceCommand->execute($query);
|
||||
}
|
||||
|
||||
public function getProjectById(int $id): ?Project
|
||||
{
|
||||
return Project::query()->where('id', $id)->first();
|
||||
}
|
||||
|
||||
public function getProjectByCode(string $code): ?Project
|
||||
{
|
||||
return Project::query()->where('code', $code)->first();
|
||||
}
|
||||
|
||||
public function isExistsCode(string $code, ?int $exceptId = null): bool
|
||||
{
|
||||
return Project::query()
|
||||
->where('code', $code)
|
||||
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||
$query->where('id', '!=', $exceptId);
|
||||
})
|
||||
->withTrashed()
|
||||
->exists();
|
||||
}
|
||||
}
|
61
app/application/app/Repositories/RoleRepository.php
Normal file
61
app/application/app/Repositories/RoleRepository.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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
|
||||
{
|
||||
$roles = Role::query()
|
||||
->with(['morph'])
|
||||
->when($withExcepts, function (Builder $query, array $withExcepts) {
|
||||
$query->withTrashed()->whereNull('deleted_at')->orWhereIn('id', $withExcepts);
|
||||
})->get();
|
||||
return $roles->pluck('name_with_morph', 'id')->toArray();
|
||||
}
|
||||
|
||||
public function isExistsCode(string $code, ?int $exceptId = null): bool
|
||||
{
|
||||
return Role::query()
|
||||
->where('code', $code)
|
||||
->whereNull('morphable_type')
|
||||
->whereNull('morphable_id')
|
||||
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||
$query->where('id', '!=', $exceptId);
|
||||
})
|
||||
->withTrashed()
|
||||
->exists();
|
||||
}
|
||||
}
|
25
app/application/app/Repositories/StorageRepository.php
Normal file
25
app/application/app/Repositories/StorageRepository.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Storage;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Contracts\Search;
|
||||
|
||||
final readonly class StorageRepository
|
||||
{
|
||||
public function __construct(
|
||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||
) { }
|
||||
|
||||
public function getStorageById(int $id): ?Storage
|
||||
{
|
||||
return Storage::query()->where('id', $id)->first();
|
||||
}
|
||||
|
||||
public function getStoregsByIds(array $ids): Search
|
||||
{
|
||||
$query = Storage::query()->whereIn('id', $ids);
|
||||
return $this->createSearchInstanceCommand->execute($query);
|
||||
}
|
||||
}
|
50
app/application/app/Repositories/UserRepository.php
Normal file
50
app/application/app/Repositories/UserRepository.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
52
app/application/app/Rules/HttpHost.php
Normal file
52
app/application/app/Rules/HttpHost.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
final readonly class HttpHost 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->validateHttpHost($value, $fail);
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
$this->validateHttpHost($item, $fail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validateHttpHost(string $value, Closure $fail): void
|
||||
{
|
||||
$scheme = explode('://', $value, 2);
|
||||
if (count($scheme) != 2 || in_array($scheme[0], ['http', 'https'], true) !== true) {
|
||||
$fail('validation.http_host')->translate();
|
||||
return;
|
||||
}
|
||||
|
||||
$host = explode(':', $scheme[1], 2);
|
||||
if (!filter_var($host[0], FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
||||
$fail('validation.http_host')->translate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($host[1]) && (!is_numeric($host[1]) || $host[1] <= 0)) {
|
||||
$fail('validation.http_host')->translate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace App\ServiceResults\Admin\LanguageService;
|
||||
|
||||
use App\Models\ProjectLanguage;
|
||||
use App\ServiceResults\ServiceResult;
|
||||
|
||||
final class NewLanguageResult extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectLanguage $language,
|
||||
private readonly string $name,
|
||||
private readonly int $index,
|
||||
) { }
|
||||
|
||||
public function getLanguage(): ProjectLanguage
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getIndex(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user