Версия 0.1.0 #1
@ -111,6 +111,7 @@ ENTRYPOINT ["composer"]
|
||||
|
||||
|
||||
FROM BUILD AS NPM
|
||||
RUN mkdir "/.npm" && chmod -R 0777 "/.npm"
|
||||
WORKDIR /var/www/html
|
||||
STOPSIGNAL SIGTERM
|
||||
RUN apk --no-cache add nodejs npm
|
||||
|
15
app/src/app/Contracts/Models/Storage.php
Normal file
15
app/src/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;
|
||||
}
|
11
app/src/app/Contracts/StorageType.php
Normal file
11
app/src/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/src/app/Contracts/StorageType/Audio.php
Normal file
10
app/src/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/src/app/Contracts/StorageType/Image.php
Normal file
10
app/src/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/src/app/Contracts/StorageType/Video.php
Normal file
10
app/src/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/src/app/Dto/Builder/Project.php
Normal file
10
app/src/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(
|
||||
|
||||
) { }
|
||||
}
|
23
app/src/app/Dto/Service/Admin/Language/NewLanguage.php
Normal file
23
app/src/app/Dto/Service/Admin/Language/NewLanguage.php
Normal file
@ -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/src/app/Dto/Service/Admin/Project/Index.php
Normal file
21
app/src/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/src/app/Dto/Service/Admin/Project/Language.php
Normal file
41
app/src/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/src/app/Dto/Service/Admin/Project/Languages.php
Normal file
18
app/src/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;
|
||||
}
|
||||
}
|
48
app/src/app/Dto/Service/Admin/Project/StoreUpdate.php
Normal file
48
app/src/app/Dto/Service/Admin/Project/StoreUpdate.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -3,22 +3,19 @@
|
||||
namespace App\Dto\Service\Admin\Role;
|
||||
|
||||
use App\Dto\Builder\Role;
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Dto\Service\Pages;
|
||||
|
||||
final readonly class Index extends Dto
|
||||
final readonly class Index extends Pages
|
||||
{
|
||||
public function __construct(
|
||||
private Role $roleBuilderDto,
|
||||
private int $page
|
||||
) { }
|
||||
int $page
|
||||
) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
|
||||
public function getRoleBuilderDto(): Role
|
||||
{
|
||||
return $this->roleBuilderDto;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,19 @@
|
||||
namespace App\Dto\Service\Admin\User;
|
||||
|
||||
use App\Dto\Builder\User;
|
||||
use App\Dto\Service\Dto;
|
||||
use App\Dto\Service\Pages;
|
||||
|
||||
final readonly class Index extends Dto
|
||||
final readonly class Index extends Pages
|
||||
{
|
||||
public function __construct(
|
||||
private User $userBuilderDto,
|
||||
private int $page
|
||||
) { }
|
||||
int $page
|
||||
) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
|
||||
public function getUserBuilderDto(): User
|
||||
{
|
||||
return $this->userBuilderDto;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
||||
|
15
app/src/app/Dto/Service/Pages.php
Normal file
15
app/src/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;
|
||||
}
|
||||
}
|
29
app/src/app/Dto/Service/Storage/File.php
Normal file
29
app/src/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/src/app/Dto/Service/Storage/Storage.php
Normal file
57
app/src/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/src/app/Dto/Service/Storage/Storages.php
Normal file
73
app/src/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/src/app/Dto/Service/Storage/Upload.php
Normal file
32
app/src/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;
|
||||
}
|
||||
}
|
31
app/src/app/Enums/Morph.php
Normal file
31
app/src/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;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ enum Permission: string
|
||||
case AdminPanel = 'allow-admin-panel';
|
||||
case Role = 'role';
|
||||
case User = 'user';
|
||||
case Project = 'project';
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
|
53
app/src/app/Enums/StorageType.php
Normal file
53
app/src/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;
|
||||
}
|
||||
}
|
@ -5,4 +5,5 @@ namespace App\Enums;
|
||||
enum SystemRole: string
|
||||
{
|
||||
case Admin = 'admin';
|
||||
case AdminProject = 'admin-project';
|
||||
}
|
||||
|
8
app/src/app/Exceptions/Dto/Storage/FileException.php
Normal file
8
app/src/app/Exceptions/Dto/Storage/FileException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Dto\Storage;
|
||||
|
||||
final class FileException extends \Exception
|
||||
{
|
||||
|
||||
}
|
8
app/src/app/Exceptions/Dto/Storage/StorageException.php
Normal file
8
app/src/app/Exceptions/Dto/Storage/StorageException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\Exceptions\Dto\Storage;
|
||||
|
||||
final class StorageException extends \Exception
|
||||
{
|
||||
|
||||
}
|
8
app/src/app/Exceptions/Dto/Storage/StoragesException.php
Normal file
8
app/src/app/Exceptions/Dto/Storage/StoragesException.php
Normal file
@ -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\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
|
||||
{
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ namespace App\Helpers;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final readonly class Helpers
|
||||
{
|
||||
@ -22,4 +23,18 @@ final readonly class Helpers
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
25
app/src/app/Http/Controllers/Admin/LanguagesController.php
Normal file
25
app/src/app/Http/Controllers/Admin/LanguagesController.php
Normal file
@ -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));
|
||||
}
|
||||
}
|
93
app/src/app/Http/Controllers/Admin/ProjectsController.php
Normal file
93
app/src/app/Http/Controllers/Admin/ProjectsController.php
Normal file
@ -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());
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ final class RolesController extends Controller
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
queryWith: ['morph']
|
||||
);
|
||||
|
||||
$result = $this->roleService->index($data->getRoleBuilderDto(), $querySettingsDto, $user);
|
||||
|
@ -11,7 +11,7 @@ use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class UsersController extends Controller
|
||||
final class UsersController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserService $userService
|
||||
|
10
app/src/app/Http/Controllers/Storage/Controller.php
Normal file
10
app/src/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
|
||||
{
|
||||
|
||||
}
|
27
app/src/app/Http/Controllers/Storage/ImagesController.php
Normal file
27
app/src/app/Http/Controllers/Storage/ImagesController.php
Normal file
@ -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()));
|
||||
}
|
||||
}
|
@ -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'),
|
||||
);
|
||||
}
|
||||
}
|
30
app/src/app/Http/Requests/Admin/Projects/IndexRequest.php
Normal file
30
app/src/app/Http/Requests/Admin/Projects/IndexRequest.php
Normal file
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
104
app/src/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
Normal file
104
app/src/app/Http/Requests/Admin/Projects/StoreUpdateRequest.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
40
app/src/app/Http/Requests/Storage/ImageRequest.php
Normal file
40
app/src/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')),
|
||||
);
|
||||
}
|
||||
}
|
26
app/src/app/Http/Resources/Admin/Languages/NewLanguage.php
Normal file
26
app/src/app/Http/Resources/Admin/Languages/NewLanguage.php
Normal file
@ -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/src/app/Http/Resources/Storage/Upload.php
Normal file
23
app/src/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/src/app/Models/Project.php
Normal file
50
app/src/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/src/app/Models/ProjectLanguage.php
Normal file
50
app/src/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',
|
||||
];
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ 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
|
||||
@ -21,7 +22,7 @@ final class Role extends Model
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code'
|
||||
'code',
|
||||
];
|
||||
|
||||
public function scopeLatest(Builder $query): Builder
|
||||
@ -34,6 +35,25 @@ final class Role extends Model
|
||||
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');
|
||||
@ -49,7 +69,7 @@ final class Role extends Model
|
||||
protected function isAdmin(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => ( $this->code === SystemRoleEnum::Admin->value ),
|
||||
get: fn () => ( \is_null($this->morphable_type) && \is_null($this->morphable_id) && $this->code === SystemRoleEnum::Admin->value ),
|
||||
)->shouldCache();
|
||||
}
|
||||
}
|
||||
|
18
app/src/app/Models/Scopes/SortScope.php
Normal file
18
app/src/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/src/app/Models/Storage.php
Normal file
70
app/src/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/src/app/Models/Traits/StorageTrait.php
Normal file
26
app/src/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);
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class User extends Authenticatable
|
||||
final class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
@ -59,11 +59,6 @@ class User extends Authenticatable
|
||||
return $this->belongsToMany(Role::class);
|
||||
}
|
||||
|
||||
public function hasRole(string $role): bool
|
||||
{
|
||||
return $this->roles->where('code', $role)->isNotEmpty();
|
||||
}
|
||||
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
return $this->permissions->search($permission) !== false;
|
||||
@ -72,7 +67,11 @@ class User extends Authenticatable
|
||||
protected function isAdmin(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->hasRole(SystemRole::Admin->value),
|
||||
get: fn () => $this->roles
|
||||
->where('code', SystemRole::Admin->value)
|
||||
->whereNull('morphable_type')
|
||||
->whereNull('morphable_id')
|
||||
->isNotEmpty(),
|
||||
)->shouldCache();
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,15 @@
|
||||
|
||||
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');
|
||||
return
|
||||
$user->hasPermission('allow-admin-panel.view')
|
||||
|| $user->roles->where('code', SystemRole::AdminProject->value)->isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
43
app/src/app/Policies/ProjectPolicy.php
Normal file
43
app/src/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;
|
||||
}
|
||||
}
|
@ -2,9 +2,15 @@
|
||||
|
||||
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;
|
||||
@ -22,6 +28,19 @@ class AppServiceProvider extends ServiceProvider
|
||||
$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),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,19 +52,9 @@ class AppServiceProvider extends ServiceProvider
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
Password::defaults(function () {
|
||||
$rule = Password::min(8);
|
||||
$this->passwordDefaults();
|
||||
|
||||
if ($this->app->isProduction()) {
|
||||
$rule->letters()
|
||||
->mixedCase()
|
||||
->numbers()
|
||||
->symbols()
|
||||
->uncompromised();
|
||||
}
|
||||
|
||||
return $rule;
|
||||
});
|
||||
Relation::enforceMorphMap(Morph::map());
|
||||
|
||||
$this->configureRateLimiting();
|
||||
Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']);
|
||||
@ -63,4 +72,21 @@ class AppServiceProvider extends ServiceProvider
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
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/src/app/Repositories/ProjectRepository.php
Normal file
49
app/src/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();
|
||||
}
|
||||
}
|
@ -38,18 +38,20 @@ final readonly class RoleRepository
|
||||
|
||||
public function getRolesForSelect(array $withExcepts = []): array
|
||||
{
|
||||
return Role::query()
|
||||
$roles = Role::query()
|
||||
->with(['morph'])
|
||||
->when($withExcepts, function (Builder $query, array $withExcepts) {
|
||||
$query->withTrashed()->whereNull('deleted_at')->orWhereIn('id', $withExcepts);
|
||||
})
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
})->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);
|
||||
})
|
||||
|
25
app/src/app/Repositories/StorageRepository.php
Normal file
25
app/src/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);
|
||||
}
|
||||
}
|
52
app/src/app/Rules/HttpHost.php
Normal file
52
app/src/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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\ServiceResults\Storage;
|
||||
|
||||
use App\Dto\Service\Storage\Storage as StorageDto;
|
||||
use App\Models\Storage as StorageModel;
|
||||
use App\ServiceResults\ServiceResult;
|
||||
|
||||
final class SaveDeleteStorageResult extends ServiceResult
|
||||
{
|
||||
private array $storagesForDelete = [];
|
||||
private array $storagesForSave = [];
|
||||
|
||||
public function addDelete(StorageDto $storageDto): void
|
||||
{
|
||||
$type = $storageDto->getStorageType();
|
||||
if (!isset($this->storagesForDelete[$type->value])) {
|
||||
$this->storagesForDelete[$type->value] = [
|
||||
'ids' => [],
|
||||
'type' => $type,
|
||||
'isDeleteType' => ($storageDto->isMany() !== true),
|
||||
];
|
||||
}
|
||||
|
||||
if ($storageDto->isFile()) {
|
||||
$id = $storageDto->getFile()->getId();
|
||||
$this->storagesForDelete[$type->value]['ids'][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
public function addSave(StorageModel $storage, StorageDto $storageDto): void
|
||||
{
|
||||
$this->storagesForSave[] = [
|
||||
'storage' => $storage,
|
||||
'isMany' => $storageDto->isMany(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getStoragesForDelete(): array
|
||||
{
|
||||
return $this->storagesForDelete;
|
||||
}
|
||||
|
||||
public function getStoragesForSave(): array
|
||||
{
|
||||
return $this->storagesForSave;
|
||||
}
|
||||
}
|
18
app/src/app/ServiceResults/Storage/UploadResult.php
Normal file
18
app/src/app/ServiceResults/Storage/UploadResult.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace App\ServiceResults\Storage;
|
||||
|
||||
use App\Models\Storage;
|
||||
use App\ServiceResults\ServiceResult;
|
||||
|
||||
final class UploadResult extends ServiceResult
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Storage $storage,
|
||||
) { }
|
||||
|
||||
public function getStorage(): Storage
|
||||
{
|
||||
return $this->storage;
|
||||
}
|
||||
}
|
25
app/src/app/Services/Admin/LanguageService.php
Normal file
25
app/src/app/Services/Admin/LanguageService.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Dto\Service\Admin\Language\NewLanguage;
|
||||
use App\Models\ProjectLanguage;
|
||||
use App\ServiceResults\Admin\LanguageService\NewLanguageResult;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\Services\Service;
|
||||
|
||||
final class LanguageService extends Service
|
||||
{
|
||||
public function newLanguage(NewLanguage $data): ServiceResultError | NewLanguageResult
|
||||
{
|
||||
$language = new ProjectLanguage();
|
||||
$language->title = "";
|
||||
$language->code = "";
|
||||
|
||||
return new NewLanguageResult(
|
||||
language: $language,
|
||||
name: $data->getName(),
|
||||
index: $data->getIndex(),
|
||||
);
|
||||
}
|
||||
}
|
199
app/src/app/Services/Admin/ProjectService.php
Normal file
199
app/src/app/Services/Admin/ProjectService.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Dto\Builder\Project as ProjectBuilderDto;
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Dto\Service\Admin\Project\StoreUpdate;
|
||||
use App\Enums\Morph;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLanguage;
|
||||
use App\Models\User;
|
||||
use App\Repositories\ProjectRepository;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
use App\Services\Project\ProjectCommandHandler;
|
||||
use App\Services\ProjectLanguage\ModelSyncCommand;
|
||||
use App\Services\Role\CreateAdminRoleForProjectCommand;
|
||||
use App\Services\Service;
|
||||
use App\Services\Storage\StorageService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class ProjectService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectRepository $projectRepository,
|
||||
private readonly ProjectCommandHandler $projectCommandHandler,
|
||||
private readonly CreateAdminRoleForProjectCommand $createAdminRoleForProjectCommand,
|
||||
private readonly ModelSyncCommand $languageModelSyncCommand,
|
||||
private readonly StorageService $storageService,
|
||||
) { }
|
||||
|
||||
public function index(ProjectBuilderDto $projectBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
if ($user->cannot('viewAny', Project::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
$projects = $this->projectRepository->getProjects(
|
||||
$projectBuilderDto,
|
||||
$querySettingsDto->getQueryWith()
|
||||
)->pagination(
|
||||
$querySettingsDto->getLimit(),
|
||||
$querySettingsDto->getPage()
|
||||
);
|
||||
|
||||
return $this->result([
|
||||
'projects' => $projects,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
if ($user->cannot('create', Project::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
$language = new ProjectLanguage();
|
||||
$language->is_default = true;
|
||||
$language->title = $user->lang?->getTitle();
|
||||
$language->code = $user->lang?->getLocale();
|
||||
return $this->result([
|
||||
'project' => new Project(),
|
||||
'languages' => collect([$language])->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
$project = $this->projectRepository->getProjectById($id);
|
||||
|
||||
if (is_null($project)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('view', $project)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
return $this->result([
|
||||
'project' => $project,
|
||||
'languages' => $project->languages->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
if ($user->cannot('create', Project::class)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
if ($this->projectRepository->isExistsCode($data->getCode())) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.code')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.code')])]
|
||||
);
|
||||
}
|
||||
|
||||
$storages = $this->storageService->getStoragesAndValidate($data->getStorages(), Morph::Project);
|
||||
if (!$storages->isSuccess()) {
|
||||
return $storages;
|
||||
}
|
||||
|
||||
try {
|
||||
$project = DB::transaction(function () use ($data, $user, $storages) {
|
||||
$dataProject = $this->getDataProject($data);
|
||||
|
||||
$project = $this->projectCommandHandler->handleStore($dataProject);
|
||||
$this->createAdminRoleForProjectCommand->execute($project, $user);
|
||||
$this->languageModelSyncCommand->execute($project, $data->getLanguages());
|
||||
$this->storageService->saveAndDelete($project, $storages);
|
||||
|
||||
return $project;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($project, __('The project was successfully created'));
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||
{
|
||||
$project = $this->projectRepository->getProjectById($id);
|
||||
|
||||
if (is_null($project)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('update', $project)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
if ($this->projectRepository->isExistsCode($data->getCode(), $project->id)) {
|
||||
return $this->errValidate(
|
||||
__('validation.unique', ['attribute' => __('validation.attributes.code')]),
|
||||
['code' => __('validation.unique', ['attribute' => __('validation.attributes.code')])]
|
||||
);
|
||||
}
|
||||
|
||||
$storages = $this->storageService->getStoragesAndValidate($data->getStorages(), Morph::Project, $project->id);
|
||||
if (!$storages->isSuccess()) {
|
||||
return $storages;
|
||||
}
|
||||
|
||||
try {
|
||||
$project = DB::transaction(function () use ($data, $project, $storages) {
|
||||
$dataProject = $this->getDataProject($data);
|
||||
|
||||
$project = $this->projectCommandHandler->handleUpdate($project, $dataProject);
|
||||
$this->languageModelSyncCommand->execute($project, $data->getLanguages());
|
||||
$this->storageService->saveAndDelete($project, $storages);
|
||||
|
||||
return $project;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->resultStoreUpdateModel($project, __('The project was successfully updated'));
|
||||
}
|
||||
|
||||
public function destroy(int $id, User $user): ServiceResultError|ServiceResultSuccess
|
||||
{
|
||||
$project = $this->projectRepository->getProjectById($id);
|
||||
|
||||
if (is_null($project)) {
|
||||
return $this->errNotFound(__('Not Found'));
|
||||
}
|
||||
|
||||
if ($user->cannot('delete', $project)) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($project) {
|
||||
$this->projectCommandHandler->handleDestroy($project);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return $this->ok(__('The project has been deleted'));
|
||||
}
|
||||
|
||||
private function getDataProject(StoreUpdate $data): array
|
||||
{
|
||||
return [
|
||||
'name' => $data->getName(),
|
||||
'code' => $data->getCode(),
|
||||
'http_host' => $data->getHttpHost(),
|
||||
'is_public' => $data->isPublic(),
|
||||
];
|
||||
}
|
||||
}
|
15
app/src/app/Services/Project/BuilderCommand.php
Normal file
15
app/src/app/Services/Project/BuilderCommand.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Project;
|
||||
|
||||
use App\Dto\Builder\Project as ProjectBuilderDto;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
final readonly class BuilderCommand
|
||||
{
|
||||
public function execute(Relation | Builder $query, ProjectBuilderDto $projectBuilderDto): Relation | Builder
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
}
|
26
app/src/app/Services/Project/ProjectCommandHandler.php
Normal file
26
app/src/app/Services/Project/ProjectCommandHandler.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
|
||||
final readonly class ProjectCommandHandler
|
||||
{
|
||||
public function handleStore(array $data): Project
|
||||
{
|
||||
return Project::create($data);
|
||||
}
|
||||
|
||||
public function handleUpdate(Project $project, array $data): Project
|
||||
{
|
||||
$project->update($data);
|
||||
$project->touch();
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
public function handleDestroy(Project $project): void
|
||||
{
|
||||
$project->delete();
|
||||
}
|
||||
}
|
74
app/src/app/Services/ProjectLanguage/ModelSyncCommand.php
Normal file
74
app/src/app/Services/ProjectLanguage/ModelSyncCommand.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\ProjectLanguage;
|
||||
|
||||
use App\Dto\Service\Admin\Project\Language;
|
||||
use App\Dto\Service\Admin\Project\Languages;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
final readonly class ModelSyncCommand
|
||||
{
|
||||
public function execute(Project $project, Languages $languages): void
|
||||
{
|
||||
$projectLanguages = $project->languages()->get();
|
||||
$this->deleteLanguages($languages, $projectLanguages);
|
||||
|
||||
$languagesInsert = [];
|
||||
$languagesUpdate = [];
|
||||
foreach ($languages->getLanguages() as $language) {
|
||||
/** @var Language $language */
|
||||
$data = [
|
||||
'code' => $language->getCode(),
|
||||
'title' => $language->getTitle(),
|
||||
'sort' => $language->getSort(),
|
||||
'is_default' => $language->isDefault(),
|
||||
];
|
||||
|
||||
if ($language->getId() !== null) {
|
||||
$projectLanguage = $projectLanguages->firstWhere('id', $language->getId());
|
||||
if ($projectLanguage !== null) {
|
||||
|
||||
$projectLanguageFix = $projectLanguages->firstWhere('code', $language->getCode());
|
||||
if ($projectLanguageFix !== null && !in_array($projectLanguageFix->id, $languagesUpdate)) {
|
||||
// Fixing a bug when language codes are swapped.
|
||||
$projectLanguageFix->update([
|
||||
'code' => $projectLanguageFix->code . '-' . $projectLanguageFix->id,
|
||||
]);
|
||||
}
|
||||
|
||||
$projectLanguage->update($data);
|
||||
$languagesUpdate[] = $projectLanguage->id;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$languagesInsert[] = $data;
|
||||
}
|
||||
|
||||
if (!empty($languagesInsert)) {
|
||||
$project->languages()->createMany($languagesInsert);
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteLanguages(Languages $languages, Collection $projectLanguages): void
|
||||
{
|
||||
$languagesIdUpdate = [];
|
||||
foreach ($languages->getLanguages() as $language) {
|
||||
/** @var Language $language */
|
||||
if ($language->getId() === null) {
|
||||
continue;
|
||||
}
|
||||
$languagesIdUpdate[] = $language->getId();
|
||||
}
|
||||
|
||||
foreach ($projectLanguages as $projectLanguage) {
|
||||
if (in_array($projectLanguage->id, $languagesIdUpdate)) {
|
||||
continue;
|
||||
}
|
||||
$projectLanguage->update([
|
||||
'code' => $projectLanguage->code . '-' . $projectLanguage->id,
|
||||
]);
|
||||
$projectLanguage->delete();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Role;
|
||||
|
||||
use App\Enums\SystemRole;
|
||||
use App\Models\Project;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Services\User\UserCommandHandler;
|
||||
|
||||
final readonly class CreateAdminRoleForProjectCommand
|
||||
{
|
||||
public function __construct(
|
||||
private UserCommandHandler $userCommandHandler,
|
||||
){ }
|
||||
|
||||
public function execute(Project $project, User $user): Role
|
||||
{
|
||||
$data = [
|
||||
'name' => 'Administrator',
|
||||
'code' => SystemRole::AdminProject->value,
|
||||
|
||||
];
|
||||
$role = $project->roles()->create($data);
|
||||
$this->userCommandHandler->attachRole($user, $role);
|
||||
|
||||
return $role;
|
||||
}
|
||||
}
|
51
app/src/app/Services/Storage/Image/ResizeCommandHandler.php
Normal file
51
app/src/app/Services/Storage/Image/ResizeCommandHandler.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Storage\Image;
|
||||
|
||||
use App\Models\Storage;
|
||||
use Intervention\Image\Laravel\Facades\Image as InterventionImage;
|
||||
|
||||
final readonly class ResizeCommandHandler
|
||||
{
|
||||
public function resize(Storage $storage, int $width, int $height): Storage
|
||||
{
|
||||
$image = InterventionImage::read($storage->path);
|
||||
if ($image->width() < $width && $image->height() < $height) {
|
||||
return $storage;
|
||||
}
|
||||
|
||||
$image->scale($width, $height)
|
||||
->save($storage->path);
|
||||
|
||||
$storage->touch();
|
||||
return $storage;
|
||||
}
|
||||
|
||||
public function width(Storage $storage, int $width): Storage
|
||||
{
|
||||
$image = InterventionImage::read($storage->path);
|
||||
if ($image->width() < $width) {
|
||||
return $storage;
|
||||
}
|
||||
|
||||
$image->scale(width: $width)
|
||||
->save($storage->path);
|
||||
|
||||
$storage->touch();
|
||||
return $storage;
|
||||
}
|
||||
|
||||
public function height(Storage $storage, int $height): Storage
|
||||
{
|
||||
$image = InterventionImage::read($storage->path);
|
||||
if ($image->height() < $height) {
|
||||
return $storage;
|
||||
}
|
||||
|
||||
$image->scale(height: $height)
|
||||
->save($storage->path);
|
||||
|
||||
$storage->touch();
|
||||
return $storage;
|
||||
}
|
||||
}
|
65
app/src/app/Services/Storage/ImageService.php
Normal file
65
app/src/app/Services/Storage/ImageService.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Storage;
|
||||
|
||||
use app\Dto\Service\Storage\Upload;
|
||||
use App\Models\Storage;
|
||||
use App\Models\User;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\Storage\UploadResult;
|
||||
use App\Services\Service;
|
||||
use App\Services\Storage\Image\ResizeCommandHandler;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class ImageService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly StorageCommandHandler $storageCommandHandler,
|
||||
private readonly ResizeCommandHandler $resizeCommandHandler,
|
||||
private readonly int $maxImageWidth,
|
||||
private readonly int $maxImageHeight,
|
||||
) { }
|
||||
|
||||
public function uploadAndResize(Upload $upload, User $user): ServiceResultError | UploadResult
|
||||
{
|
||||
if ($user->cannot('upload', $upload->getMorph()->getPathModel())) {
|
||||
return $this->errFobidden(__('Access is denied'));
|
||||
}
|
||||
|
||||
if ($upload->getStorageType()->isImage() !== true) {
|
||||
return $this->errValidate(__('storage.Trying to upload a wrong image'));
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = DB::transaction(function () use ($upload, $user) {
|
||||
$storage = $this->storageCommandHandler->handleStore(
|
||||
upload: $upload,
|
||||
user: $user,
|
||||
);
|
||||
return $this->imageResize($storage, $this->maxImageWidth, $this->maxImageHeight);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService(__('Server Error'));
|
||||
}
|
||||
|
||||
return new UploadResult($storage);
|
||||
}
|
||||
|
||||
private function imageResize(Storage $storage, ?int $width, ?int $height): Storage
|
||||
{
|
||||
if (empty($width) && empty($height)) {
|
||||
return $storage;
|
||||
}
|
||||
|
||||
if (!empty($width) && !empty($height)) {
|
||||
return $this->resizeCommandHandler->resize($storage, $width, $height);
|
||||
}
|
||||
|
||||
if (!empty($height)) {
|
||||
return $this->resizeCommandHandler->height($storage, $height);
|
||||
}
|
||||
|
||||
return $this->resizeCommandHandler->width($storage, $width);
|
||||
}
|
||||
}
|
91
app/src/app/Services/Storage/StorageCommandHandler.php
Normal file
91
app/src/app/Services/Storage/StorageCommandHandler.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Storage;
|
||||
|
||||
use App\Dto\Service\Storage\Upload;
|
||||
use App\Enums\StorageType;
|
||||
use app\Exceptions\Services\Storage\StorageCommandHandlerException;
|
||||
use App\Exceptions\Services\Storage\StorageSaveFileException;
|
||||
use App\Models\Storage;
|
||||
use App\Models\User;
|
||||
use App\Contracts\Models\Storage as ModelStorageContract;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final readonly class StorageCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private string $disc,
|
||||
) { }
|
||||
|
||||
public function handleStore(Upload $upload, User $user, array $data = []): Storage
|
||||
{
|
||||
$folder = '/' . $upload->getMorph()->getFolderName();
|
||||
$folder .= date("/Y/m/d");
|
||||
$folder .= '/' . $upload->getStorageType()->getFolderName();
|
||||
$folder = Str::lower($folder);
|
||||
$path = $upload->getFile()->store($folder, $this->disc);
|
||||
|
||||
if ($path === false) {
|
||||
throw new StorageSaveFileException('Could not save the file: ' . $upload->getFile()->getFilename() . '.');
|
||||
}
|
||||
|
||||
$storage = new Storage();
|
||||
$storage->created_user_id = $user->id;
|
||||
$storage->file = '/' . $path;
|
||||
$storage->type = $upload->getStorageType();
|
||||
$storage->morph_type = $upload->getMorph()->value;
|
||||
|
||||
if (!empty($data)) {
|
||||
$storage->fill($data);
|
||||
}
|
||||
$storage->save();
|
||||
|
||||
return $storage;
|
||||
}
|
||||
|
||||
public function handleStorageAttachModel(Storage $storage, ModelStorageContract $model): Storage
|
||||
{
|
||||
if ($storage->morph_id === $model->id) {
|
||||
return $storage;
|
||||
}
|
||||
|
||||
if (!\is_null($storage->morph_id)) {
|
||||
throw new StorageCommandHandlerException('The model is already attached!');
|
||||
}
|
||||
|
||||
$storage->morph_id = $model->id;
|
||||
$storage->save();
|
||||
|
||||
return $storage;
|
||||
}
|
||||
|
||||
public function handleUpdate(Storage $storage, array $data = []): Storage
|
||||
{
|
||||
$storage->update($data);
|
||||
return $storage;
|
||||
}
|
||||
|
||||
public function handleDestroy(Storage $storage): void
|
||||
{
|
||||
$storage->delete();
|
||||
}
|
||||
|
||||
public function handleDestroyByIds(array $ids): void
|
||||
{
|
||||
if (!empty($ids)) {
|
||||
Storage::whereIn('id', $ids)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function handleDestroyByModel(ModelStorageContract $model, StorageType $type): void
|
||||
{
|
||||
$model->storage()->where('type', $type)->delete();
|
||||
}
|
||||
|
||||
public function handleDestroyByModelStorageIds(ModelStorageContract $model, array $ids, StorageType $type): void
|
||||
{
|
||||
if (!empty($ids)) {
|
||||
$model->storage()->whereIn('id', $ids)->where('type', $type)->delete();
|
||||
}
|
||||
}
|
||||
}
|
72
app/src/app/Services/Storage/StorageService.php
Normal file
72
app/src/app/Services/Storage/StorageService.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Storage;
|
||||
|
||||
use App\Dto\Service\Storage\Storage;
|
||||
use App\Dto\Service\Storage\Storages;
|
||||
use App\Enums\Morph;
|
||||
use App\Contracts\Models\Storage as ModelStorageContract;
|
||||
use App\Repositories\StorageRepository;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\Storage\SaveDeleteStorageResult;
|
||||
use App\Services\Service;
|
||||
|
||||
class StorageService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly StorageRepository $storageRepository,
|
||||
private readonly ValidationService $storageValidation,
|
||||
private readonly StorageCommandHandler $storageCommandHandler,
|
||||
) { }
|
||||
|
||||
public function getStoragesAndValidate(Storages $storages, Morph $morph, ?int $modelId = null): ServiceResultError | SaveDeleteStorageResult
|
||||
{
|
||||
$storageModels = $this->storageRepository->getStoregsByIds($storages->getAllStorageIds())->all();
|
||||
$result = new SaveDeleteStorageResult();
|
||||
|
||||
foreach ($storages->getAllStorages() as $storage) {
|
||||
/** @var Storage $storage */
|
||||
if ($storage->isDelete()) {
|
||||
$result->addDelete($storage);
|
||||
continue;
|
||||
}
|
||||
if (!$storage->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storageModel = $storageModels->firstWhere('id', $storage->getFile()->getId());
|
||||
if (\is_null($storageModel)) {
|
||||
return $this->errValidate(__('storage.File_not_found'));
|
||||
}
|
||||
$validate = $this->storageValidation->execute($storageModel, $morph, $storage->getStorageType(), $modelId);
|
||||
if (!$validate->isSuccess()) {
|
||||
return $this->errValidate($validate->getMessage());
|
||||
}
|
||||
|
||||
$result->addSave($storageModel, $storage);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function saveAndDelete(ModelStorageContract $model, SaveDeleteStorageResult $storageResult): void
|
||||
{
|
||||
foreach ($storageResult->getStoragesForDelete() as $dataStorage) {
|
||||
$type = $dataStorage['type'];
|
||||
if ($dataStorage['isDeleteType']) {
|
||||
$this->storageCommandHandler->handleDestroyByModel($model, $type);
|
||||
}
|
||||
if ($dataStorage['isDeleteType'] || empty($dataStorage['ids'])) {
|
||||
continue;
|
||||
}
|
||||
$this->storageCommandHandler->handleDestroyByModelStorageIds($model, $dataStorage['ids'], $type);
|
||||
}
|
||||
foreach ($storageResult->getStoragesForSave() as $dataStorage) {
|
||||
$storage = $dataStorage['storage'];
|
||||
if ($dataStorage['isMany'] !== true && $storage->morph_id !== $model->id) {
|
||||
$this->storageCommandHandler->handleDestroyByModel($model, $storage->type);
|
||||
}
|
||||
$this->storageCommandHandler->handleStorageAttachModel($storage, $model);
|
||||
}
|
||||
}
|
||||
}
|
48
app/src/app/Services/Storage/ValidationService.php
Normal file
48
app/src/app/Services/Storage/ValidationService.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace app\Services\Storage;
|
||||
|
||||
use App\Enums\Morph;
|
||||
use App\Enums\StorageType;
|
||||
use App\Models\Storage as StorageModel;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\Services\Service;
|
||||
|
||||
final class ValidationService extends Service
|
||||
{
|
||||
public function execute(StorageModel $storageModel, Morph $morph, StorageType $storageType, ?int $modelId = null): ServiceResultError | ServiceResultSuccess
|
||||
{
|
||||
if (! $this->isMorphMatched($storageModel, $morph, $modelId)) {
|
||||
return $this->errValidate(__('storage.Attempt to replace a file'));
|
||||
}
|
||||
|
||||
if (! $this->isTypeMatched($storageModel, $storageType)) {
|
||||
return $this->errValidate(__('storage.Wrong file type'));
|
||||
}
|
||||
|
||||
return $this->ok(__('OK'));
|
||||
}
|
||||
|
||||
private function isMorphMatched(StorageModel $storageModel, Morph $morph, ?int $modelId = null): bool
|
||||
{
|
||||
if ($storageModel->morph_type !== $morph->value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\is_null($storageModel->morph_id) && $storageModel->morph_id !== $modelId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isTypeMatched(StorageModel $storageModel, StorageType $type): bool
|
||||
{
|
||||
if ($storageModel->type !== $type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Services\User;
|
||||
|
||||
use App\Dto\User\ManyRoleDto;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@ -50,6 +51,16 @@ final readonly class UserCommandHandler
|
||||
$user->roles()->sync($roles->toArray());
|
||||
}
|
||||
|
||||
public function attachRole(User $user, Role $role): void
|
||||
{
|
||||
$user->roles()->attach($role);
|
||||
}
|
||||
|
||||
public function detachRole(User $user, Role $role): void
|
||||
{
|
||||
$user->roles()->detach($user);
|
||||
}
|
||||
|
||||
private function hashPassword(int|string $password): string
|
||||
{
|
||||
return Hash::make($password);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@ -11,6 +12,8 @@ abstract class PrivateLayout extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
Paginator::useBootstrapFive();
|
||||
|
||||
return view('layout.private', [
|
||||
'navigation' => $this->getNavigation(),
|
||||
]);
|
||||
|
@ -30,12 +30,12 @@ final class Checkbox extends Form
|
||||
return (string) old($this->getRequestName(), $this->checkboxValue);
|
||||
}
|
||||
|
||||
public function getUserValue(): string
|
||||
private function getUserValue(): string
|
||||
{
|
||||
return (string) $this->userValue;
|
||||
}
|
||||
|
||||
public function getNotCheckedValue(): ?string
|
||||
private function getNotCheckedValue(): ?string
|
||||
{
|
||||
return $this->notCheckedValue;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use App\Helpers\Helpers;
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@ -19,13 +19,7 @@ abstract class Form extends Component
|
||||
return $this->requestName;
|
||||
}
|
||||
|
||||
$this->requestName = Str::of($this->getName())
|
||||
->replace(
|
||||
['.', '[', ']'],
|
||||
['_', '.', ''],
|
||||
)
|
||||
->rtrim('.')
|
||||
->value();
|
||||
$this->requestName = Helpers::formatAttributeNameToRequestName($this->getName());
|
||||
|
||||
return $this->requestName;
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ final class Input extends Form
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly string $type = 'text',
|
||||
private readonly ?string $value = ''
|
||||
private readonly ?string $value = '',
|
||||
private readonly ?string $example = null,
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
@ -34,6 +35,11 @@ final class Input extends Form
|
||||
return (string) old($this->getRequestName(), $this->value);
|
||||
}
|
||||
|
||||
private function getExample(): ?string
|
||||
{
|
||||
return $this->example;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@ -44,7 +50,8 @@ final class Input extends Form
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'type' => $this->getType(),
|
||||
'value' => $this->getValue()
|
||||
'value' => $this->getValue(),
|
||||
'example' => $this->getExample(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
34
app/src/app/View/Components/Volt/Forms/InputTypeHidden.php
Normal file
34
app/src/app/View/Components/Volt/Forms/InputTypeHidden.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class InputTypeHidden extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $name,
|
||||
private readonly ?string $value = null,
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getValue(): ?string
|
||||
{
|
||||
return (string) old($this->getRequestName(), $this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.input-type-hidden', [
|
||||
'name' => $this->getName(),
|
||||
'requestName' => $this->getRequestName(),
|
||||
'value' => $this->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
54
app/src/app/View/Components/Volt/Forms/Languages.php
Normal file
54
app/src/app/View/Components/Volt/Forms/Languages.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Languages extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $name,
|
||||
private readonly array $value,
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getValue(): array
|
||||
{
|
||||
$value = old($this->getRequestName(), null);
|
||||
if (\is_null($value)) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
$langs = [];
|
||||
$default = $value['default'] ?? null;
|
||||
if ($default !== null) {
|
||||
$default = (int) $default;
|
||||
}
|
||||
foreach ($value['items'] as $index => $lang) {
|
||||
$langs[$index] = [
|
||||
'title' => $lang['title'] ?? '',
|
||||
'code' => $lang['code'] ?? '',
|
||||
'is_default' => ($index === $default),
|
||||
'sort' => $lang['sort'] ?? '',
|
||||
'id' => $lang['id'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return $langs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.languages', [
|
||||
'name' => $this->getName(),
|
||||
'value' => $this->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -22,26 +22,17 @@ final class PermissionsForRole extends Form
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getValue(): Collection
|
||||
{
|
||||
$value = old($this->getRequestName(), $this->value);
|
||||
return collect($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Role
|
||||
*/
|
||||
private function getRole(): Role
|
||||
{
|
||||
return $this->role;
|
||||
|
75
app/src/app/View/Components/Volt/Forms/Upload/Image.php
Normal file
75
app/src/app/View/Components/Volt/Forms/Upload/Image.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Volt\Forms\Upload;
|
||||
|
||||
use App\Enums\Morph;
|
||||
use App\Models\Storage;
|
||||
use App\Repositories\StorageRepository;
|
||||
use App\View\Components\Volt\Forms\Form;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
use App\Contracts\StorageType\Image as ImageContract;
|
||||
|
||||
final class Image extends Form
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $name,
|
||||
private readonly Morph $morph,
|
||||
private readonly ImageContract $storageType,
|
||||
private readonly StorageRepository $storageRepository,
|
||||
private readonly ?Storage $storage = null,
|
||||
) { }
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
return Str::ucfirst($this->title);
|
||||
}
|
||||
|
||||
private function getMorph(): Morph
|
||||
{
|
||||
return $this->morph;
|
||||
}
|
||||
|
||||
private function getStorageType(): ImageContract
|
||||
{
|
||||
return $this->storageType;
|
||||
}
|
||||
|
||||
private function getAcceptedFiles(): Collection
|
||||
{
|
||||
return collect($this->getStorageType()->getAcceptMimes());
|
||||
}
|
||||
|
||||
private function getStorage(): ?Storage
|
||||
{
|
||||
$name = $this->getRequestName();
|
||||
$storageId = old($name . '.file');
|
||||
if (!empty($storageId)) {
|
||||
return $this->storageRepository->getStorageById((int) $storageId);
|
||||
}
|
||||
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.volt.forms.upload.image', [
|
||||
'storage' => $this->getStorage(),
|
||||
'title' => $this->getTitle(),
|
||||
'name' => $this->getName(),
|
||||
'morph' => $this->getMorph(),
|
||||
'storageType' => $this->getStorageType(),
|
||||
'acceptedFiles' => $this->getAcceptedFiles(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
Intervention\Image\Laravel\ServiceProvider::class,
|
||||
App\Providers\AppServiceProvider::class,
|
||||
];
|
||||
|
@ -6,6 +6,7 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"intervention/image-laravel": "^1.2",
|
||||
"kor-elf/captcha-rule-for-laravel": "^1.0",
|
||||
"laravel/framework": "^11.0",
|
||||
"laravel/tinker": "^2.9"
|
||||
|
215
app/src/composer.lock
generated
215
app/src/composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9e88c9e1f78fbf9527a1fc1d837b132d",
|
||||
"content-hash": "69cad3ad961845edc7e2ac7c0f9fb5e6",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -1045,6 +1045,219 @@
|
||||
],
|
||||
"time": "2023-12-03T19:50:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"version": "4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/gif.git",
|
||||
"reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/gif/zipball/3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
|
||||
"reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Gif\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Native PHP GIF Encoder/Decoder",
|
||||
"homepage": "https://github.com/intervention/gif",
|
||||
"keywords": [
|
||||
"animation",
|
||||
"gd",
|
||||
"gif",
|
||||
"image"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/gif/issues",
|
||||
"source": "https://github.com/Intervention/gif/tree/4.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-26T17:23:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"version": "3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image.git",
|
||||
"reference": "67be90e5700370c88833190d4edc07e4bb7d157b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/67be90e5700370c88833190d4edc07e4bb7d157b",
|
||||
"reference": "67be90e5700370c88833190d4edc07e4bb7d157b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^4.0.1",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^1",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-exif": "Recommended to be able to read EXIF data properly."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "PHP image manipulation",
|
||||
"homepage": "https://image.intervention.io/",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image/issues",
|
||||
"source": "https://github.com/Intervention/image/tree/3.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-22T07:12:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image-laravel",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image-laravel.git",
|
||||
"reference": "d30b62fea3c49896dbf26ea7799e7d718e779310"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image-laravel/zipball/d30b62fea3c49896dbf26ea7799e7d718e779310",
|
||||
"reference": "d30b62fea3c49896dbf26ea7799e7d718e779310",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/support": "^8|^9|^10|^11",
|
||||
"intervention/image": "^3",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^8.18",
|
||||
"phpunit/phpunit": "^10.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Intervention\\Image\\Laravel\\ServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Image": "Intervention\\Image\\Laravel\\Facades\\Image"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\Laravel\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Integration of Intervention Image",
|
||||
"homepage": "https://image.intervention.io/",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"laravel",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image-laravel/issues",
|
||||
"source": "https://github.com/Intervention/image-laravel/tree/1.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-03T09:14:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "kor-elf/captcha-rule-for-laravel",
|
||||
"version": "1.0.1",
|
||||
|
@ -44,6 +44,14 @@ return [
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'storage' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public/storage'),
|
||||
'url' => env('APP_URL') . '/storage/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
|
21
app/src/config/image.php
Normal file
21
app/src/config/image.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Image Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Intervention Image supports “GD Library” and “Imagick” to process images
|
||||
| internally. Depending on your PHP setup, you can choose one of them.
|
||||
|
|
||||
| Included options:
|
||||
| - \Intervention\Image\Drivers\Gd\Driver::class
|
||||
| - \Intervention\Image\Drivers\Imagick\Driver::class
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => \Intervention\Image\Drivers\Gd\Driver::class
|
||||
|
||||
];
|
9
app/src/config/storage.php
Normal file
9
app/src/config/storage.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
return [
|
||||
// to resize
|
||||
'max_image_width' => 1000,
|
||||
// to resize
|
||||
'max_image_height' => 1000,
|
||||
// filesystems
|
||||
'disk' => 'storage',
|
||||
];
|
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('projects', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->index();
|
||||
$table->string('code')->unique();
|
||||
$table->string('http_host')->nullable()->unique()->comment('Scheme and Domain and Port (example: http://localhost:8080).');
|
||||
$table->boolean('is_public')->default(0)->index();
|
||||
$table->timestamps();
|
||||
$table->softDeletes()->index();
|
||||
$table->index('created_at');
|
||||
$table->index(['is_public', 'deleted_at']);
|
||||
});
|
||||
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->unsignedInteger('morph_type')->nullable();
|
||||
$table->unsignedBigInteger('morph_id')->nullable();
|
||||
$table->index(['morph_type', 'morph_id']);
|
||||
$table->dropUnique(['code']);
|
||||
$table->unique(['code', 'morph_type', 'morph_id']);
|
||||
});
|
||||
|
||||
Schema::create('storage', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('file');
|
||||
$table->unsignedInteger('morph_type');
|
||||
$table->unsignedBigInteger('morph_id')->nullable()->index();
|
||||
$table->unsignedInteger('type')->comment('File type (main photo, video, gallery, etc.).');
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->unsignedBigInteger('created_user_id')->nullable();
|
||||
$table->foreign('created_user_id')->references('id')->on('users');
|
||||
$table->index(['morph_type', 'morph_id']);
|
||||
});
|
||||
|
||||
Schema::create('project_languages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('project_id')->index();
|
||||
$table->foreign('project_id')->references('id')->on('projects');
|
||||
$table->string('code');
|
||||
$table->string('title');
|
||||
$table->boolean('is_default');
|
||||
$table->integer('sort')->index();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->index(['project_id', 'deleted_at']);
|
||||
$table->unique(['project_id', 'code']);
|
||||
});
|
||||
|
||||
Schema::create('project_content', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title')->index();
|
||||
$table->unsignedBigInteger('project_id')->index();
|
||||
$table->foreign('project_id')->references('id')->on('projects');
|
||||
$table->unsignedBigInteger('language_id')->index();
|
||||
$table->foreign('language_id')->references('id')->on('project_languages');
|
||||
$table->longText('description');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->index(['project_id', 'language_id', 'deleted_at']);
|
||||
});
|
||||
|
||||
Schema::create('project_links', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->string('link');
|
||||
$table->unsignedBigInteger('project_id')->index();
|
||||
$table->foreign('project_id')->references('id')->on('projects');
|
||||
$table->unsignedBigInteger('language_id')->nullable()->index();
|
||||
$table->foreign('language_id')->references('id')->on('project_languages');
|
||||
$table->index(['project_id', 'language_id', 'deleted_at']);
|
||||
$table->integer('sort')->index();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('project_links');
|
||||
Schema::dropIfExists('project_content');
|
||||
Schema::dropIfExists('project_languages');
|
||||
Schema::dropIfExists('storage');
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropUnique(['code', 'morph_type', 'morph_id']);
|
||||
$table->dropColumn('morph_type');
|
||||
$table->dropColumn('morph_id');
|
||||
$table->unique('code');
|
||||
});
|
||||
Schema::dropIfExists('projects');
|
||||
}
|
||||
};
|
@ -238,5 +238,15 @@
|
||||
"The group has been deleted": "The group has been deleted",
|
||||
"The user was successfully created": "The user was successfully created",
|
||||
"The user was successfully updated": "The user was successfully updated",
|
||||
"The user has been deleted": "The user has been deleted"
|
||||
"The user has been deleted": "The user has been deleted",
|
||||
"example:": "example:",
|
||||
"or": "or",
|
||||
"The project was successfully created": "The project was successfully created",
|
||||
"The project was successfully updated": "The project was successfully updated",
|
||||
"The project has been deleted": "The project has been deleted",
|
||||
"Languages": "Languages",
|
||||
"Add language": "Add language",
|
||||
"There was an error adding a language": "There was an error adding a language",
|
||||
"Select images": "Select images",
|
||||
"loading": "loading"
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ return [
|
||||
'User group' => 'User group',
|
||||
'Users' => 'Users',
|
||||
'AdminPanel' => 'Admin Panel',
|
||||
'Projects' => 'Projects',
|
||||
];
|
||||
|
@ -9,4 +9,5 @@ return [
|
||||
'Allowed to delete' => 'Allowed to delete',
|
||||
'Administrative panel allowed' => 'Administrative panel allowed',
|
||||
'AdminPanel' => 'Administrative panel allowed',
|
||||
'Project' => 'Projects',
|
||||
];
|
||||
|
7
app/src/lang/en/storage.php
Normal file
7
app/src/lang/en/storage.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return [
|
||||
'Trying to upload a wrong image' => 'Trying to upload a wrong image!',
|
||||
'File_not_found' => 'File not found.',
|
||||
'Attempt to replace a file' => 'Attempt to replace a file.',
|
||||
'Wrong file type' => 'Wrong file type.',
|
||||
];
|
@ -150,6 +150,7 @@ return [
|
||||
'uuid' => 'The :attribute must be a valid UUID.',
|
||||
'no_type' => 'The :attribute can only use: :type.',
|
||||
'captcha' => 'Failed to pass human verification.',
|
||||
'http_host' => 'The :attribute must be a valid domain.',
|
||||
'attributes' => [
|
||||
'address' => 'address',
|
||||
'affiliate_url' => 'affiliate URL',
|
||||
@ -277,5 +278,19 @@ return [
|
||||
'permissions' => 'permissions',
|
||||
'is_active' => 'is active',
|
||||
'roles' => 'user group',
|
||||
'is_public' => 'public',
|
||||
'http_host' => 'hostname',
|
||||
'sort' => 'sorting',
|
||||
'is_default' => 'default',
|
||||
'language-code' => 'language code',
|
||||
'languages.items.*.code' => 'language code',
|
||||
'languages.items.*.title' => 'language title',
|
||||
'languages.items.*.sort' => 'language sorting',
|
||||
'languages.items.*.id' => 'language ID',
|
||||
'languages.default' => 'default language',
|
||||
'language-default' => 'default language',
|
||||
'logo' => 'logo',
|
||||
'logo.file' => 'logo file',
|
||||
'logo.delete' => 'remove logo',
|
||||
],
|
||||
];
|
||||
|
@ -238,5 +238,15 @@
|
||||
"The group has been deleted": "Группа была удалена",
|
||||
"The user was successfully created": "Пользователь был успешно создан",
|
||||
"The user was successfully updated": "Пользователь был успешно обновлен",
|
||||
"The user has been deleted": "Пользователь был удален"
|
||||
"The user has been deleted": "Пользователь был удален",
|
||||
"example:": "пример:",
|
||||
"or": "или",
|
||||
"The project was successfully created": "Проект был успешно создан",
|
||||
"The project was successfully updated": "Проект был успешно обновлен",
|
||||
"The project has been deleted": "Проект был удален",
|
||||
"Languages": "Языки",
|
||||
"Add language": "Добавить язык",
|
||||
"There was an error adding a language": "Произошла ошибка при добавлении языка",
|
||||
"Select images": "Выберите изображения",
|
||||
"loading": "загрузка"
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ return [
|
||||
'User group' => 'Группа пользователей',
|
||||
'Users' => 'Пользователи',
|
||||
'AdminPanel' => 'Админка',
|
||||
'Projects' => 'Проекты',
|
||||
];
|
||||
|
@ -10,4 +10,5 @@ return [
|
||||
'Allowed to delete' => 'Разрешено удалять',
|
||||
'Administrative panel allowed' => 'Административная панель разрешена',
|
||||
'AdminPanel' => 'Административная панель разрешена',
|
||||
'Project' => 'Проекты',
|
||||
];
|
||||
|
7
app/src/lang/ru/storage.php
Normal file
7
app/src/lang/ru/storage.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return [
|
||||
'Trying to upload a wrong image' => 'Попытка загрузить не картинку!',
|
||||
'File_not_found' => 'Файл не найден.',
|
||||
'Attempt to replace a file' => 'Попытка подменить файл.',
|
||||
'Wrong file type' => 'Неправильный тип файла.',
|
||||
];
|
@ -150,6 +150,7 @@ return [
|
||||
'uuid' => 'Значение поля :attribute должно быть корректным UUID.',
|
||||
'no_type' => 'Значение поля :attribute может использовать только: :type.',
|
||||
'captcha' => 'Не удалось пройти проверку человеком.',
|
||||
'http_host' => 'Значение поля :attribute не является доменом или имеет некорректный формат.',
|
||||
'attributes' => [
|
||||
'address' => 'адрес',
|
||||
'affiliate_url' => 'Партнёрская ссылка',
|
||||
@ -213,7 +214,7 @@ return [
|
||||
'minute' => 'минута',
|
||||
'mobile' => 'моб. номер',
|
||||
'month' => 'месяц',
|
||||
'name' => 'имя',
|
||||
'name' => 'название',
|
||||
'national_code' => 'национальный код',
|
||||
'number' => 'номер',
|
||||
'password' => 'пароль',
|
||||
@ -264,7 +265,7 @@ return [
|
||||
'test_name' => 'тестовое имя',
|
||||
'text' => 'текст',
|
||||
'time' => 'время',
|
||||
'title' => 'наименование',
|
||||
'title' => 'заголовок',
|
||||
'type' => 'тип',
|
||||
'updated_at' => 'обновлено в',
|
||||
'user' => 'пользователь',
|
||||
@ -277,5 +278,19 @@ return [
|
||||
'permissions' => 'разрешения',
|
||||
'is_active' => 'активен',
|
||||
'roles' => 'группа пользователей',
|
||||
'is_public' => 'публичный',
|
||||
'http_host' => 'имя хоста',
|
||||
'sort' => 'сортировка',
|
||||
'is_default' => 'по умолчанию',
|
||||
'language-code' => 'код языка',
|
||||
'languages.items.*.code' => 'код языка',
|
||||
'languages.items.*.title' => 'заголовок языка',
|
||||
'languages.items.*.sort' => 'языковая сортировка',
|
||||
'languages.items.*.id' => 'ID языка',
|
||||
'languages.default' => 'язык по умолчанию',
|
||||
'language-default' => 'язык по умолчанию',
|
||||
'logo' => 'логотип',
|
||||
'logo.file' => 'файл логотипа',
|
||||
'logo.delete' => 'удалить логотип',
|
||||
],
|
||||
];
|
||||
|
20
app/src/package-lock.json
generated
20
app/src/package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"bootstrap": "5.0.2",
|
||||
"chartist": "^0.11.4",
|
||||
"chartist-plugin-tooltips": "^0.0.17",
|
||||
"dropzone": "^6.0.0-beta.2",
|
||||
"notyf": "^3.10.0",
|
||||
"nouislider": "^15.2.0",
|
||||
"onscreen": "^1.4.0",
|
||||
@ -667,6 +668,11 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.2.14.tgz",
|
||||
"integrity": "sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA=="
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.56.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.6.tgz",
|
||||
@ -1153,6 +1159,15 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dropzone": {
|
||||
"version": "6.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/dropzone/-/dropzone-6.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-k44yLuFFhRk53M8zP71FaaNzJYIzr99SKmpbO/oZKNslDjNXQsBTdfLs+iONd0U0L94zzlFzRnFdqbLcs7h9fQ==",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.2.13",
|
||||
"just-extend": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.722",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.722.tgz",
|
||||
@ -1465,6 +1480,11 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/just-extend": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-5.1.1.tgz",
|
||||
"integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ=="
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.2.tgz",
|
||||
|
@ -10,6 +10,7 @@
|
||||
"bootstrap": "5.0.2",
|
||||
"chartist": "^0.11.4",
|
||||
"chartist-plugin-tooltips": "^0.0.17",
|
||||
"dropzone": "^6.0.0-beta.2",
|
||||
"notyf": "^3.10.0",
|
||||
"nouislider": "^15.2.0",
|
||||
"onscreen": "^1.4.0",
|
||||
|
@ -9,6 +9,23 @@
|
||||
<span class="sidebar-text">{{ __('admin-sections.Dashboard') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@can('viewAny', \App\Models\Project::class)
|
||||
<li @class([
|
||||
'nav-item',
|
||||
'active' => request()->route()->named('admin.projects.*'),
|
||||
])>
|
||||
<a href="{{ route('admin.projects.index') }}" class="nav-link">
|
||||
<span class="sidebar-icon">
|
||||
<svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 0 1-1.125-1.125v-3.75ZM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-8.25ZM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-2.25Z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="sidebar-text">{{ __('admin-sections.Projects') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
@can('viewAny', \App\Models\User::class)
|
||||
<li @class([
|
||||
'nav-item',
|
||||
|
11
app/src/resources/views/admin/projects/_from.blade.php
Normal file
11
app/src/resources/views/admin/projects/_from.blade.php
Normal file
@ -0,0 +1,11 @@
|
||||
@csrf
|
||||
<x-volt.forms.checkbox :title="__('validation.attributes.is_public')" name="is_public" checkboxValue="1" notCheckedValue="0" :userValue="(string) $project->is_public" />
|
||||
<x-volt.forms.upload.image :title="__('validation.attributes.logo')" :storage="$project->getStorageOne(\App\Enums\StorageType::Logo)" name="logo" :morph="\App\Enums\Morph::Project" :storageType="\App\Enums\StorageType::Logo" />
|
||||
<x-volt.forms.input :title="__('validation.attributes.name')" name="name" type="text" :value="$project->name" required autofocus />
|
||||
<x-volt.forms.input :title="__('validation.attributes.code')" name="code" type="text" :value="$project->code" required />
|
||||
<x-volt.forms.input :title="__('validation.attributes.http_host')" :example="'<strong>https://localhost.com</strong> ' . __('or') . ' <strong>http://localhost.com:8080</strong>'" name="http_host" type="text" :value="$project->http_host" />
|
||||
<x-volt.forms.languages name="languages" :value="$languages" />
|
||||
|
||||
@canany(['create', 'update'], $project)
|
||||
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
||||
@endcanany
|
8
app/src/resources/views/admin/projects/_top.blade.php
Normal file
8
app/src/resources/views/admin/projects/_top.blade.php
Normal file
@ -0,0 +1,8 @@
|
||||
@can('create', \App\Models\Project::class)
|
||||
<div class="mb-4">
|
||||
<a href="{{ route('admin.projects.create') }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||
{{ __('Create') }}
|
||||
</a>
|
||||
</div>
|
||||
@endcan
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user