Версия 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
		Reference in New Issue
	
	Block a user