Merge pull request 'Версия 0.3.0' (#3) from develop into main
Reviewed-on: #3
This commit is contained in:
		@@ -1,15 +1,20 @@
 | 
				
			|||||||
APP_NAME=Laravel
 | 
					APP_NAME="My Projects Website"
 | 
				
			||||||
APP_ENV=local
 | 
					APP_ENV=local
 | 
				
			||||||
APP_KEY=
 | 
					APP_KEY=
 | 
				
			||||||
APP_DEBUG=true
 | 
					APP_DEBUG=true
 | 
				
			||||||
APP_TIMEZONE=UTC
 | 
					APP_TIMEZONE=UTC
 | 
				
			||||||
APP_URL=http://localhost
 | 
					APP_URL=http://localhost
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					APP_CAPTCHA=false
 | 
				
			||||||
CAPTCHA_API_DOMAIN=http://your-domain-captcha-or-IP:8081
 | 
					CAPTCHA_API_DOMAIN=http://your-domain-captcha-or-IP:8081
 | 
				
			||||||
CAPTCHA_PRIVATE_TOKEN=
 | 
					CAPTCHA_PRIVATE_TOKEN=
 | 
				
			||||||
CAPTCHA_STATIC_PATH=http://your-domain-captcha-or-IP:8081/captcha
 | 
					CAPTCHA_STATIC_PATH=http://your-domain-captcha-or-IP:8081/captcha
 | 
				
			||||||
CAPTCHA_PUBLIC_TOKEN=
 | 
					CAPTCHA_PUBLIC_TOKEN=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Don't forget to configure MAIL_MAILER to send mail.
 | 
				
			||||||
 | 
					FEEDBACK_MAIL_NOTIFICATIONS=false
 | 
				
			||||||
 | 
					FEEDBACK_MAIL_TO=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
APP_FORCE_HTTPS=false
 | 
					APP_FORCE_HTTPS=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
APP_DEFAULT_LOCALE=ru
 | 
					APP_DEFAULT_LOCALE=ru
 | 
				
			||||||
@@ -58,7 +63,7 @@ REDIS_HOST=app-redis
 | 
				
			|||||||
REDIS_PASSWORD=null
 | 
					REDIS_PASSWORD=null
 | 
				
			||||||
REDIS_PORT=6379
 | 
					REDIS_PORT=6379
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MAIL_MAILER=log
 | 
					MAIL_MAILER=smtp
 | 
				
			||||||
MAIL_HOST=127.0.0.1
 | 
					MAIL_HOST=127.0.0.1
 | 
				
			||||||
MAIL_PORT=2525
 | 
					MAIL_PORT=2525
 | 
				
			||||||
MAIL_USERNAME=null
 | 
					MAIL_USERNAME=null
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Console\Commands\Files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Services\Commands\DeleteOldFilesService;
 | 
				
			||||||
 | 
					use Illuminate\Console\Command;
 | 
				
			||||||
 | 
					use Illuminate\Support\Carbon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class DeleteOldFilesFromStorage extends Command
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The name and signature of the console command.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $signature = 'files:delete-old-files-from-storage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The console command description.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $description = 'Remove temporary files or files that have been deleted from the storage table';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create a new command instance.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        parent::__construct();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Execute the console command.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function handle(DeleteOldFilesService $deleteOldFilesService): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $temporaryBeforeDate = Carbon::now()->subDays(3);
 | 
				
			||||||
 | 
					        $deletedBeforeDate   = Carbon::now()->subDays(7);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $result = $deleteOldFilesService->fromStorage($temporaryBeforeDate, $deletedBeforeDate);
 | 
				
			||||||
 | 
					        if ($result->isError()) {
 | 
				
			||||||
 | 
					            $this->error($result->getMessage());
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $this->info($result->getMessage());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,12 +3,14 @@
 | 
				
			|||||||
namespace App\Dto\Service\Admin\Project\About;
 | 
					namespace App\Dto\Service\Admin\Project\About;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Dto\Service\Dto;
 | 
					use App\Dto\Service\Dto;
 | 
				
			||||||
 | 
					use App\Dto\Service\Storage\Storages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final readonly class StoreUpdate extends Dto
 | 
					final readonly class StoreUpdate extends Dto
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private string $title,
 | 
					        private string $title,
 | 
				
			||||||
        private string $description,
 | 
					        private string $description,
 | 
				
			||||||
 | 
					        private Storages $storages,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getTitle(): string
 | 
					    public function getTitle(): string
 | 
				
			||||||
@@ -20,4 +22,9 @@ final readonly class StoreUpdate extends Dto
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->description;
 | 
					        return $this->description;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getStorages(): Storages
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->storages;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Dto\Service\Admin\Project\DocumentationContent;
 | 
					namespace App\Dto\Service\Admin\Project\DocumentationContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Storage\Storages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final readonly class Content
 | 
					final readonly class Content
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private int    $languageId,
 | 
					        private int    $languageId,
 | 
				
			||||||
        private string $title,
 | 
					        private string $title,
 | 
				
			||||||
        private string $content,
 | 
					        private string $content,
 | 
				
			||||||
 | 
					        private Storages $storages,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getLanguageId(): int
 | 
					    public function getLanguageId(): int
 | 
				
			||||||
@@ -24,4 +27,9 @@ final readonly class Content
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->content;
 | 
					        return $this->content;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getStorages(): Storages
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->storages;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\Service\DocumentationContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\Storage\Storages;
 | 
				
			||||||
 | 
					use App\Models\DocumentationContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class StorageDto
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var array [][DocumentationContent documentationContent, Storages storages]
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private array $storages = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function add(DocumentationContent $documentationContent, Storages $storages): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->storages[] = [
 | 
				
			||||||
 | 
					            'documentationContent' => $documentationContent,
 | 
				
			||||||
 | 
					            'storages'             => $storages
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return array [][DocumentationContent documentationContent, Storages storages]
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getStorages(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->storages;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Dto\View\Volt\Form;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\Morph;
 | 
				
			||||||
 | 
					use App\Helpers\Helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class WysiwygStorageUpload
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private string      $inputName,
 | 
				
			||||||
 | 
					        private Morph       $morph,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getInputName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->inputName;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getRequestInputName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Helpers::formatAttributeNameToRequestName($this->getInputName());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getMorph(): Morph
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->morph;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,16 +2,22 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Enums;
 | 
					namespace App\Enums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\DocumentationContent;
 | 
				
			||||||
use App\Models\Project;
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					use App\Models\ProjectContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum Morph: int
 | 
					enum Morph: int
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    case Project = 1;
 | 
					    case Project = 1;
 | 
				
			||||||
 | 
					    case DocumentationContent = 2;
 | 
				
			||||||
 | 
					    case ProjectContent = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getPathModel(): string
 | 
					    public function getPathModel(): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return match ($this) {
 | 
					        return match ($this) {
 | 
				
			||||||
            self::Project              => Project::class,
 | 
					            self::Project              => Project::class,
 | 
				
			||||||
 | 
					            self::DocumentationContent => DocumentationContent::class,
 | 
				
			||||||
 | 
					            self::ProjectContent       => ProjectContent::class,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,11 +9,13 @@ use App\Contracts\StorageType\Video;
 | 
				
			|||||||
enum StorageType: int implements Image, Video, Audio
 | 
					enum StorageType: int implements Image, Video, Audio
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    case Logo = 1;
 | 
					    case Logo = 1;
 | 
				
			||||||
 | 
					    case ContentImages = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getTitle(): string
 | 
					    public function getTitle(): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return match ($this) {
 | 
					        return match ($this) {
 | 
				
			||||||
            self::Logo => __('validation.attributes.logo'),
 | 
					            self::Logo => __('validation.attributes.logo'),
 | 
				
			||||||
 | 
					            self::ContentImages => __('validation.attributes.content_images'),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,6 +23,7 @@ enum StorageType: int implements Image, Video, Audio
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return match ($this) {
 | 
					        return match ($this) {
 | 
				
			||||||
            self::Logo          => ['jpeg', 'jpg', 'png'],
 | 
					            self::Logo          => ['jpeg', 'jpg', 'png'],
 | 
				
			||||||
 | 
					            self::ContentImages => ['jpeg', 'jpg', 'png'],
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,20 +31,21 @@ enum StorageType: int implements Image, Video, Audio
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return match ($this) {
 | 
					        return match ($this) {
 | 
				
			||||||
            self::Logo          => true,
 | 
					            self::Logo          => true,
 | 
				
			||||||
 | 
					            self::ContentImages => true,
 | 
				
			||||||
            default => false
 | 
					            default => false
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function isVideo(): bool
 | 
					    public function isVideo(): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return match ($this->name) {
 | 
					        return match ($this) {
 | 
				
			||||||
            default => false
 | 
					            default => false
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function isAudio(): bool
 | 
					    public function isAudio(): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return match ($this->name) {
 | 
					        return match ($this) {
 | 
				
			||||||
            default => false
 | 
					            default => false
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Exceptions\Services\DocumentationContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class StorageCommandException extends \Exception
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(private readonly ServiceResultError $resultError, string $message = "", int $code = 0, ?\Throwable $previous = null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        parent::__construct($message, $code, $previous);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getResultError(): ServiceResultError
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->resultError;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -19,7 +19,9 @@ final class AuthController extends Controller
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function login(): View
 | 
					    public function login(): View
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return view('login');
 | 
					        return view('login', [
 | 
				
			||||||
 | 
					            'captcha' => config('app.captcha', false),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function authorization(AuthorizationRequest $request): RedirectResponse
 | 
					    public function authorization(AuthorizationRequest $request): RedirectResponse
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ final class FeedbackController extends Controller
 | 
				
			|||||||
        return view('site.feedback.index', [
 | 
					        return view('site.feedback.index', [
 | 
				
			||||||
            'project'             => $request->get('project'),
 | 
					            'project'             => $request->get('project'),
 | 
				
			||||||
            'websiteTranslations' => $request->get('websiteTranslations'),
 | 
					            'websiteTranslations' => $request->get('websiteTranslations'),
 | 
				
			||||||
 | 
					            'captcha'             => config('app.captcha', false),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,10 +4,19 @@ namespace App\Http\Requests\Admin\Projects\About;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use App\Contracts\FormRequestDto;
 | 
					use App\Contracts\FormRequestDto;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\About\StoreUpdate;
 | 
					use App\Dto\Service\Admin\Project\About\StoreUpdate;
 | 
				
			||||||
 | 
					use App\Dto\Service\Storage\Storages;
 | 
				
			||||||
 | 
					use App\Enums\StorageType;
 | 
				
			||||||
use Illuminate\Foundation\Http\FormRequest;
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
					final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    public function attributes(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'storage.content_images.*.file' => __('validation.attributes.content_images'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the validation rules that apply to the request.
 | 
					     * Get the validation rules that apply to the request.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -16,6 +25,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
        return [
 | 
					        return [
 | 
				
			||||||
            'title' => ['required', 'string', 'max:255',],
 | 
					            'title' => ['required', 'string', 'max:255',],
 | 
				
			||||||
            'description' => ['nullable', 'string',],
 | 
					            'description' => ['nullable', 'string',],
 | 
				
			||||||
 | 
					            'storage.content_images.*.file' => ['numeric', 'min:1'],
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,6 +34,18 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
        return new StoreUpdate(
 | 
					        return new StoreUpdate(
 | 
				
			||||||
            title: $this->input('title'),
 | 
					            title: $this->input('title'),
 | 
				
			||||||
            description: $this->input('description'),
 | 
					            description: $this->input('description'),
 | 
				
			||||||
 | 
					            storages: $this->storages(),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function storages(): Storages
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $storages = new Storages();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $content = $this->input('storage', []);
 | 
				
			||||||
 | 
					        $images = $content['content_images'] ?? [];
 | 
				
			||||||
 | 
					        $storages->addMany($images, StorageType::ContentImages);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $storages;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,10 +6,19 @@ use App\Contracts\FormRequestDto;
 | 
				
			|||||||
use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
 | 
					use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
 | 
					use App\Dto\Service\Admin\Project\DocumentationContent\Content;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
 | 
					use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
 | 
				
			||||||
 | 
					use App\Dto\Service\Storage\Storages;
 | 
				
			||||||
 | 
					use App\Enums\StorageType;
 | 
				
			||||||
use Illuminate\Foundation\Http\FormRequest;
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
					final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    public function attributes(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'content.*.content_images.*.file' => __('validation.attributes.content_images'),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the validation rules that apply to the request.
 | 
					     * Get the validation rules that apply to the request.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -22,6 +31,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
            'category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'],
 | 
					            'category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'],
 | 
				
			||||||
            'content.*.title' => ['required', 'string', 'max:255'],
 | 
					            'content.*.title' => ['required', 'string', 'max:255'],
 | 
				
			||||||
            'content.*.content' => ['nullable', 'string'],
 | 
					            'content.*.content' => ['nullable', 'string'],
 | 
				
			||||||
 | 
					            'content.*.content_images.*.file' => ['numeric', 'min:1'],
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,8 +59,19 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
                languageId: (int) $languageId,
 | 
					                languageId: (int) $languageId,
 | 
				
			||||||
                title:      $content['title'],
 | 
					                title:      $content['title'],
 | 
				
			||||||
                content:    $content['content'] ?? '',
 | 
					                content:    $content['content'] ?? '',
 | 
				
			||||||
 | 
					                storages:   $this->contentStorages($content),
 | 
				
			||||||
            ));
 | 
					            ));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return $contents;
 | 
					        return $contents;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function contentStorages(array $content): Storages
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $storages = new Storages();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $images = $content['content_images'] ?? [];
 | 
				
			||||||
 | 
					        $storages->addMany($images, StorageType::ContentImages);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $storages;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,12 +13,17 @@ final class AuthorizationRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function rules(): array
 | 
					    public function rules(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return [
 | 
					        $rules = [
 | 
				
			||||||
            'email' => ['required', 'email', 'max:255'],
 | 
					            'email' => ['required', 'email', 'max:255'],
 | 
				
			||||||
            'password' => ['required', 'min:3'],
 | 
					            'password' => ['required', 'min:3'],
 | 
				
			||||||
            'captcha-verified' => ['captcha'],
 | 
					 | 
				
			||||||
            'remember' => ['nullable', 'boolean'],
 | 
					            'remember' => ['nullable', 'boolean'],
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (config('app.captcha', false)) {
 | 
				
			||||||
 | 
					            $rules['captcha-verified'] = ['captcha'];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $rules;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getDto(): Authorization
 | 
					    public function getDto(): Authorization
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,12 +32,17 @@ final class SendRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function rules(): array
 | 
					    public function rules(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return [
 | 
					        $rules = [
 | 
				
			||||||
            'name'    => ['nullable', 'string', 'max:255'],
 | 
					            'name'    => ['nullable', 'string', 'max:255'],
 | 
				
			||||||
            'email'   => ['nullable', 'string', 'max:255', 'email'],
 | 
					            'email'   => ['nullable', 'string', 'max:255', 'email'],
 | 
				
			||||||
            'message' => ['required', 'string', 'max:5000'],
 | 
					            'message' => ['required', 'string', 'max:5000'],
 | 
				
			||||||
            'captcha-verified' => ['captcha'],
 | 
					 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (config('app.captcha', false)) {
 | 
				
			||||||
 | 
					            $rules['captcha-verified'] = ['captcha'];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $rules;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getDto(): Send
 | 
					    public function getDto(): Send
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,9 @@ use App\Contracts\FormRequestDto;
 | 
				
			|||||||
use App\Dto\Service\Storage\Upload;
 | 
					use App\Dto\Service\Storage\Upload;
 | 
				
			||||||
use App\Enums\Morph;
 | 
					use App\Enums\Morph;
 | 
				
			||||||
use App\Enums\StorageType;
 | 
					use App\Enums\StorageType;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Validation\Validator;
 | 
				
			||||||
use Illuminate\Foundation\Http\FormRequest;
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					use Illuminate\Http\Exceptions\HttpResponseException;
 | 
				
			||||||
use Illuminate\Validation\Rules\Enum;
 | 
					use Illuminate\Validation\Rules\Enum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class ImageRequest extends FormRequest implements FormRequestDto
 | 
					final class ImageRequest extends FormRequest implements FormRequestDto
 | 
				
			||||||
@@ -37,4 +39,20 @@ final class ImageRequest extends FormRequest implements FormRequestDto
 | 
				
			|||||||
            morph:        Morph::from((int) $this->input('morph')),
 | 
					            morph:        Morph::from((int) $this->input('morph')),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the error messages for the defined validation rules.*
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function failedValidation(Validator $validator): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * To always return json
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        throw new HttpResponseException(response()->json([
 | 
				
			||||||
 | 
					            'errors' => $validator->errors(),
 | 
				
			||||||
 | 
					            'status' => true
 | 
				
			||||||
 | 
					        ], 422));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Models;
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Contracts\Models\Storage as StorageContract;
 | 
				
			||||||
 | 
					use App\Models\Traits\StorageTrait;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
					use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Model;
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
use Illuminate\Database\Eloquent\SoftDeletes;
 | 
					use Illuminate\Database\Eloquent\SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class DocumentationContent extends Model
 | 
					final class DocumentationContent extends Model implements StorageContract
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use HasFactory, SoftDeletes;
 | 
					    use HasFactory, SoftDeletes, StorageTrait;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected $table = 'documentation_content';
 | 
					    protected $table = 'documentation_content';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\Models;
 | 
					namespace App\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Contracts\Models\Storage as StorageContract;
 | 
				
			||||||
 | 
					use App\Models\Traits\StorageTrait;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
					use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Model;
 | 
					use Illuminate\Database\Eloquent\Model;
 | 
				
			||||||
use Illuminate\Database\Eloquent\SoftDeletes;
 | 
					use Illuminate\Database\Eloquent\SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class ProjectContent extends Model
 | 
					final class ProjectContent extends Model implements StorageContract
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use HasFactory, SoftDeletes;
 | 
					    use HasFactory, SoftDeletes, StorageTrait;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected $table = 'project_content';
 | 
					    protected $table = 'project_content';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								app/application/app/Notifications/ReviewAdded.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								app/application/app/Notifications/ReviewAdded.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Notifications;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Enums\Site\ProjectSection;
 | 
				
			||||||
 | 
					use App\Models\ProjectFeedback;
 | 
				
			||||||
 | 
					use Illuminate\Bus\Queueable;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Queue\ShouldQueue;
 | 
				
			||||||
 | 
					use Illuminate\Notifications\Messages\MailMessage;
 | 
				
			||||||
 | 
					use Illuminate\Notifications\Notification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class ReviewAdded extends Notification implements ShouldQueue
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use Queueable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create a new notification instance.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private readonly ProjectFeedback $feedback
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the notification's delivery channels.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array<int, string>
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function via(object $notifiable): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return ['mail'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the mail representation of the notification.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function toMail(object $notifiable): MailMessage
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $project = $this->feedback->project;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (new MailMessage)
 | 
				
			||||||
 | 
					                    ->subject(__('notification.Review Added: :name', ['name' => $project->name]))
 | 
				
			||||||
 | 
					                    ->line(__('notification.Added a new review.'))
 | 
				
			||||||
 | 
					                    ->action(__('notification.Project :name', ['name' => $project->name]), \url(ProjectSection::Home->url($project)))
 | 
				
			||||||
 | 
					                    ->line( __('site.attributes.name') . ': ' . $this->feedback->name)
 | 
				
			||||||
 | 
					                    ->line( __('site.attributes.email') . ': ' . $this->feedback->email)
 | 
				
			||||||
 | 
					                    ->line(__('site.attributes.message') . ': ' . $this->feedback->message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the array representation of the notification.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array<string, mixed>
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function toArray(object $notifiable): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								app/application/app/Policies/DocumentationContentPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/application/app/Policies/DocumentationContentPolicy.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Policies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class DocumentationContentPolicy extends Policy
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function upload(User $user): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($user->hasPermission('documentation.create') || $user->hasPermission('documentation.update')) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -26,4 +26,13 @@ final readonly class ProjectContentPolicy extends Policy
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $user->hasPermission('project-content.update');
 | 
					        return $user->hasPermission('project-content.update');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function upload(User $user): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if ($user->hasPermission('project-content.create') || $user->hasPermission('project-content.update')) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,10 @@
 | 
				
			|||||||
namespace App\Providers;
 | 
					namespace App\Providers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use App\Enums\Morph;
 | 
					use App\Enums\Morph;
 | 
				
			||||||
 | 
					use App\Services\ProjectFeedback\ProjectFeedbackCommandHandler;
 | 
				
			||||||
use App\Services\Search\CreateSearchInstanceCommand;
 | 
					use App\Services\Search\CreateSearchInstanceCommand;
 | 
				
			||||||
use App\Services\Search\Search;
 | 
					use App\Services\Search\Search;
 | 
				
			||||||
 | 
					use App\Services\Site\FeedbackService;
 | 
				
			||||||
use App\Services\Storage\Image\ResizeCommandHandler;
 | 
					use App\Services\Storage\Image\ResizeCommandHandler;
 | 
				
			||||||
use App\Services\Storage\ImageService;
 | 
					use App\Services\Storage\ImageService;
 | 
				
			||||||
use App\Services\Storage\StorageCommandHandler;
 | 
					use App\Services\Storage\StorageCommandHandler;
 | 
				
			||||||
@@ -31,7 +33,15 @@ class AppServiceProvider extends ServiceProvider
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->app->bind(StorageCommandHandler::class, function () {
 | 
					        $this->app->bind(StorageCommandHandler::class, function () {
 | 
				
			||||||
            return new StorageCommandHandler(disc: (string) config('storage.disk'));
 | 
					            return new StorageCommandHandler(disc: (string) \config('storage.disk'));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->app->bind(FeedbackService::class, function (Application $app) {
 | 
				
			||||||
 | 
					            return new FeedbackService(
 | 
				
			||||||
 | 
					                $app->make(ProjectFeedbackCommandHandler::class),
 | 
				
			||||||
 | 
					                (bool) \config('feedback.mail_notifications', false),
 | 
				
			||||||
 | 
					                \config('feedback.mail_to', null),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->app->bind(ImageService::class, function (Application $app) {
 | 
					        $this->app->bind(ImageService::class, function (Application $app) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,6 @@ use App\Services\Search\CreateSearchInstanceCommand;
 | 
				
			|||||||
use App\Dto\Builder\DocumentationCategory as DocumentationCategoryBuilderDto;
 | 
					use App\Dto\Builder\DocumentationCategory as DocumentationCategoryBuilderDto;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Builder;
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
 | 
					use Illuminate\Database\Eloquent\Relations\HasOne;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					 | 
				
			||||||
use Illuminate\Support\Str;
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final readonly class DocumentationCategoryRepository
 | 
					final readonly class DocumentationCategoryRepository
 | 
				
			||||||
@@ -63,7 +62,7 @@ final readonly class DocumentationCategoryRepository
 | 
				
			|||||||
            ->exists();
 | 
					            ->exists();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getForSelect(?ProjectLanguage $defaultLanguage, ?DocumentationCategory $exceptCategory = null, array $withExcepts = []): array
 | 
					    public function getForSelect(DocumentationVersion $version, ?ProjectLanguage $defaultLanguage, ?DocumentationCategory $exceptCategory = null, array $withExcepts = []): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $with = [
 | 
					        $with = [
 | 
				
			||||||
            'content' => function (HasOne $hasOne) use ($defaultLanguage) {
 | 
					            'content' => function (HasOne $hasOne) use ($defaultLanguage) {
 | 
				
			||||||
@@ -73,7 +72,7 @@ final readonly class DocumentationCategoryRepository
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $categories = DocumentationCategory::query()
 | 
					        $categories = $version->categories()
 | 
				
			||||||
            ->with($with)
 | 
					            ->with($with)
 | 
				
			||||||
            ->when($exceptCategory, function (Builder $query, DocumentationCategory $exceptCategory) {
 | 
					            ->when($exceptCategory, function (Builder $query, DocumentationCategory $exceptCategory) {
 | 
				
			||||||
                $query->whereNotIn(
 | 
					                $query->whereNotIn(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ namespace App\Services\Admin\Project;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use App\Contracts\ServiceResultError;
 | 
					use App\Contracts\ServiceResultError;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\About\StoreUpdate;
 | 
					use App\Dto\Service\Admin\Project\About\StoreUpdate;
 | 
				
			||||||
 | 
					use App\Enums\Morph;
 | 
				
			||||||
use App\Models\ProjectContent;
 | 
					use App\Models\ProjectContent;
 | 
				
			||||||
use App\Models\User;
 | 
					use App\Models\User;
 | 
				
			||||||
use App\Repositories\ProjectContentRepository;
 | 
					use App\Repositories\ProjectContentRepository;
 | 
				
			||||||
@@ -13,6 +14,7 @@ use App\ServiceResults\ServiceResultArray;
 | 
				
			|||||||
use App\ServiceResults\StoreUpdateResult;
 | 
					use App\ServiceResults\StoreUpdateResult;
 | 
				
			||||||
use App\Services\ProjectContent\ProjectContentCommandHandler;
 | 
					use App\Services\ProjectContent\ProjectContentCommandHandler;
 | 
				
			||||||
use App\Services\Service;
 | 
					use App\Services\Service;
 | 
				
			||||||
 | 
					use App\Services\Storage\StorageService;
 | 
				
			||||||
use Illuminate\Support\Facades\DB;
 | 
					use Illuminate\Support\Facades\DB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class AboutService extends Service
 | 
					final class AboutService extends Service
 | 
				
			||||||
@@ -22,6 +24,7 @@ final class AboutService extends Service
 | 
				
			|||||||
        private readonly ProjectLanguageRepository $projectLanguageRepository,
 | 
					        private readonly ProjectLanguageRepository $projectLanguageRepository,
 | 
				
			||||||
        private readonly ProjectContentRepository $projectContentRepository,
 | 
					        private readonly ProjectContentRepository $projectContentRepository,
 | 
				
			||||||
        private readonly ProjectContentCommandHandler $projectContentCommandHandler,
 | 
					        private readonly ProjectContentCommandHandler $projectContentCommandHandler,
 | 
				
			||||||
 | 
					        private readonly StorageService $storageService,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function languages(int $projectId, User $user): ServiceResultError | ServiceResultArray
 | 
					    public function languages(int $projectId, User $user): ServiceResultError | ServiceResultArray
 | 
				
			||||||
@@ -88,11 +91,19 @@ final class AboutService extends Service
 | 
				
			|||||||
        if ($user->cannot('create', ProjectContent::class)) {
 | 
					        if ($user->cannot('create', ProjectContent::class)) {
 | 
				
			||||||
            return $this->errFobidden(__('Access is denied'));
 | 
					            return $this->errFobidden(__('Access is denied'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $storages = $this->storageService->getStoragesAndValidate($data->getStorages(), Morph::ProjectContent);
 | 
				
			||||||
 | 
					        if (!$storages->isSuccess()) {
 | 
				
			||||||
 | 
					            return $storages;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $aboutProject = DB::transaction(function () use ($data, $projectId, $languageId) {
 | 
					            $aboutProject = DB::transaction(function () use ($data, $projectId, $languageId, $storages) {
 | 
				
			||||||
                $dataAboutProject = $this->getDataAboutProject($data);
 | 
					                $dataAboutProject = $this->getDataAboutProject($data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return $this->projectContentCommandHandler->handleStore($projectId, $languageId, $dataAboutProject);
 | 
					                $aboutProject = $this->projectContentCommandHandler->handleStore($projectId, $languageId, $dataAboutProject);
 | 
				
			||||||
 | 
					                $this->storageService->saveAndDelete($aboutProject, $storages);
 | 
				
			||||||
 | 
					                return $aboutProject;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } catch (\Throwable $e) {
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
            report($e);
 | 
					            report($e);
 | 
				
			||||||
@@ -108,11 +119,19 @@ final class AboutService extends Service
 | 
				
			|||||||
            return $this->errFobidden(__('Access is denied'));
 | 
					            return $this->errFobidden(__('Access is denied'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $storages = $this->storageService->getStoragesAndValidate($data->getStorages(), Morph::ProjectContent);
 | 
				
			||||||
 | 
					        if (!$storages->isSuccess()) {
 | 
				
			||||||
 | 
					            return $storages;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $aboutProject = DB::transaction(function () use ($data, $content) {
 | 
					            $aboutProject = DB::transaction(function () use ($data, $content, $storages) {
 | 
				
			||||||
                $dataAboutProject = $this->getDataAboutProject($data);
 | 
					                $dataAboutProject = $this->getDataAboutProject($data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return $this->projectContentCommandHandler->handleUpdate($content, $dataAboutProject);
 | 
					                $aboutProject = $this->projectContentCommandHandler->handleUpdate($content, $dataAboutProject);
 | 
				
			||||||
 | 
					                $this->storageService->saveAndDelete($aboutProject, $storages);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return $aboutProject;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } catch (\Throwable $e) {
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
            report($e);
 | 
					            report($e);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,7 +89,7 @@ final class DocumentationCategoryService extends Service
 | 
				
			|||||||
            'version'  => $version,
 | 
					            'version'  => $version,
 | 
				
			||||||
            'project'  => $project,
 | 
					            'project'  => $project,
 | 
				
			||||||
            'category' => new DocumentationCategory(),
 | 
					            'category' => new DocumentationCategory(),
 | 
				
			||||||
            'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage),
 | 
					            'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -119,7 +119,7 @@ final class DocumentationCategoryService extends Service
 | 
				
			|||||||
            'version'  => $version,
 | 
					            'version'  => $version,
 | 
				
			||||||
            'project'  => $project,
 | 
					            'project'  => $project,
 | 
				
			||||||
            'category' => $category,
 | 
					            'category' => $category,
 | 
				
			||||||
            'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage, $category, $withCategories),
 | 
					            'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage, $category, $withCategories),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,13 +5,13 @@ namespace App\Services\Admin\Project;
 | 
				
			|||||||
use App\Dto\Builder\Documentation as DocumentationBuilderDto;
 | 
					use App\Dto\Builder\Documentation as DocumentationBuilderDto;
 | 
				
			||||||
use App\Dto\QuerySettingsDto;
 | 
					use App\Dto\QuerySettingsDto;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
 | 
					use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
 | 
				
			||||||
 | 
					use App\Exceptions\Services\DocumentationContent\StorageCommandException;
 | 
				
			||||||
use App\Models\Documentation;
 | 
					use App\Models\Documentation;
 | 
				
			||||||
use App\Models\ProjectLanguage;
 | 
					use App\Models\ProjectLanguage;
 | 
				
			||||||
use App\Models\User;
 | 
					use App\Models\User;
 | 
				
			||||||
use App\Repositories\DocumentationCategoryRepository;
 | 
					use App\Repositories\DocumentationCategoryRepository;
 | 
				
			||||||
use App\Repositories\DocumentationRepository;
 | 
					use App\Repositories\DocumentationRepository;
 | 
				
			||||||
use App\Repositories\DocumentationVersionRepository;
 | 
					use App\Repositories\DocumentationVersionRepository;
 | 
				
			||||||
use App\Repositories\ProjectRepository;
 | 
					 | 
				
			||||||
use App\ServiceResults\ServiceResultArray;
 | 
					use App\ServiceResults\ServiceResultArray;
 | 
				
			||||||
use App\ServiceResults\ServiceResultError;
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
use App\ServiceResults\ServiceResultSuccess;
 | 
					use App\ServiceResults\ServiceResultSuccess;
 | 
				
			||||||
@@ -89,7 +89,7 @@ final class DocumentationService extends Service
 | 
				
			|||||||
            'version' => $version,
 | 
					            'version' => $version,
 | 
				
			||||||
            'project' => $project,
 | 
					            'project' => $project,
 | 
				
			||||||
            'documentation' => new Documentation(),
 | 
					            'documentation' => new Documentation(),
 | 
				
			||||||
            'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage),
 | 
					            'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -119,7 +119,7 @@ final class DocumentationService extends Service
 | 
				
			|||||||
            'version' => $version,
 | 
					            'version' => $version,
 | 
				
			||||||
            'project' => $project,
 | 
					            'project' => $project,
 | 
				
			||||||
            'documentation' => $documentation,
 | 
					            'documentation' => $documentation,
 | 
				
			||||||
            'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage, null, $withCategories),
 | 
					            'categories' => $this->documentationCategoryRepository->getForSelect($version, $defaultLanguage, null, $withCategories),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -150,6 +150,8 @@ final class DocumentationService extends Service
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                return $documentation;
 | 
					                return $documentation;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (StorageCommandException $e) {
 | 
				
			||||||
 | 
					            return $e->getResultError();
 | 
				
			||||||
        } catch (\Throwable $e) {
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
            report($e);
 | 
					            report($e);
 | 
				
			||||||
            return $this->errService(__('Server Error'));
 | 
					            return $this->errService(__('Server Error'));
 | 
				
			||||||
@@ -190,6 +192,8 @@ final class DocumentationService extends Service
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                return $documentation;
 | 
					                return $documentation;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (StorageCommandException $e) {
 | 
				
			||||||
 | 
					            return $e->getResultError();
 | 
				
			||||||
        } catch (\Throwable $e) {
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
            report($e);
 | 
					            report($e);
 | 
				
			||||||
            return $this->errService(__('Server Error'));
 | 
					            return $this->errService(__('Server Error'));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Models\Storage as StorageModel;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
 | 
					use App\ServiceResults\ServiceResultSuccess;
 | 
				
			||||||
 | 
					use App\Services\Service;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Builder;
 | 
				
			||||||
 | 
					use Illuminate\Support\Carbon;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Storage as StorageSupport;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class DeleteOldFilesService extends Service
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * $temporaryBeforeDate = date of deletion of temporary files
 | 
				
			||||||
 | 
					     * $deletedBeforeDate = date of deletion files that were marked as deleted
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param Carbon $temporaryBeforeDate
 | 
				
			||||||
 | 
					     * @param Carbon $deletedBeforeDate
 | 
				
			||||||
 | 
					     * @return ServiceResultError|ServiceResultSuccess
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function fromStorage(Carbon $temporaryBeforeDate, Carbon $deletedBeforeDate): ServiceResultError|ServiceResultSuccess
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $disk = config('storage.disk');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        StorageModel::withTrashed()
 | 
				
			||||||
 | 
					            ->where(function (Builder $query) use($temporaryBeforeDate) {
 | 
				
			||||||
 | 
					                $query->whereNull('morph_id')->where('updated_at', '<', $temporaryBeforeDate);
 | 
				
			||||||
 | 
					            })->orWhere(function (Builder $query) use($deletedBeforeDate) {
 | 
				
			||||||
 | 
					                $query->whereNotNull('deleted_at')->where('deleted_at', '<', $deletedBeforeDate);
 | 
				
			||||||
 | 
					            })->chunkById(100, function ($items) use($disk) {
 | 
				
			||||||
 | 
					                $deleteIds = [];
 | 
				
			||||||
 | 
					                foreach ($items as $item) {
 | 
				
			||||||
 | 
					                    $deleteIds[] = $item->id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (StorageSupport::disk($disk)->exists($item->file)) {
 | 
				
			||||||
 | 
					                        StorageSupport::disk($disk)->delete($item->file);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                StorageModel::withTrashed()->whereIn('id', $deleteIds)->forceDelete();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->ok(__('Old Files deleted.'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,18 +4,33 @@ namespace App\Services\DocumentationContent;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
 | 
					use App\Dto\Service\Admin\Project\DocumentationContent\Content;
 | 
				
			||||||
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
 | 
					use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
 | 
				
			||||||
 | 
					use App\Dto\Service\DocumentationContent\StorageDto;
 | 
				
			||||||
use App\Exceptions\Services\DocumentationContent\ContentSaveException;
 | 
					use App\Exceptions\Services\DocumentationContent\ContentSaveException;
 | 
				
			||||||
 | 
					use App\Exceptions\Services\DocumentationContent\StorageCommandException;
 | 
				
			||||||
use App\Models\Documentation;
 | 
					use App\Models\Documentation;
 | 
				
			||||||
 | 
					use App\Models\DocumentationContent;
 | 
				
			||||||
use App\Models\Project;
 | 
					use App\Models\Project;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final readonly class ModelSyncCommand
 | 
					final readonly class ModelSyncCommand
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private StorageCommand $storageCommand,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @throws StorageCommandException
 | 
				
			||||||
 | 
					     * @throws ContentSaveException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public function execute(Project $project, Documentation $documentation, Contents $contents): void
 | 
					    public function execute(Project $project, Documentation $documentation, Contents $contents): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        $storageDto = new StorageDto();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $languages = $project->languages;
 | 
					        $languages = $project->languages;
 | 
				
			||||||
        $documentationContents = $documentation->contents;
 | 
					        $documentationContents = $documentation->contents;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $newContents = [];
 | 
					        $newContents = [];
 | 
				
			||||||
 | 
					        $contentsStorageCreated = [];
 | 
				
			||||||
 | 
					        $contentLanguages = [];
 | 
				
			||||||
        foreach ($contents->getContents() as $content)   {
 | 
					        foreach ($contents->getContents() as $content)   {
 | 
				
			||||||
            /** @var Content $content */
 | 
					            /** @var Content $content */
 | 
				
			||||||
            $language = $languages->firstWhere('id', $content->getLanguageId());
 | 
					            $language = $languages->firstWhere('id', $content->getLanguageId());
 | 
				
			||||||
@@ -26,15 +41,29 @@ final readonly class ModelSyncCommand
 | 
				
			|||||||
            $model = $documentationContents->firstWhere('language_id', $language->id);
 | 
					            $model = $documentationContents->firstWhere('language_id', $language->id);
 | 
				
			||||||
            $data  = $this->getData($content);
 | 
					            $data  = $this->getData($content);
 | 
				
			||||||
            if (\is_null($model)) {
 | 
					            if (\is_null($model)) {
 | 
				
			||||||
 | 
					                $contentsStorageCreated[$content->getLanguageId()] = $content->getStorages();
 | 
				
			||||||
                $newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data);
 | 
					                $newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data);
 | 
				
			||||||
 | 
					                $contentLanguages[] = $content->getLanguageId();
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            $storageDto->add($model, $content->getStorages());
 | 
				
			||||||
            $model->update($data);
 | 
					            $model->update($data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!empty($newContents)) {
 | 
					        if (!empty($newContents)) {
 | 
				
			||||||
            $documentation->contents()->createMany($newContents);
 | 
					            $documentation->contents()->createMany($newContents);
 | 
				
			||||||
 | 
					            $contents = $documentation->contents()->whereIn('language_id', $contentLanguages)->get();
 | 
				
			||||||
 | 
					            foreach ($contents as $content) {
 | 
				
			||||||
 | 
					                /** @var DocumentationContent $content */
 | 
				
			||||||
 | 
					                if (!isset($contentsStorageCreated[$content->language_id])) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $storageDto->add($content, $contentsStorageCreated[$content->language_id]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->storageCommand->execute($storageDto);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private function getData(Content $content): array
 | 
					    private function getData(Content $content): array
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					<?php declare(strict_types=1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace App\Services\DocumentationContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\Service\DocumentationContent\StorageDto;
 | 
				
			||||||
 | 
					use App\Enums\Morph;
 | 
				
			||||||
 | 
					use App\Exceptions\Services\DocumentationContent\StorageCommandException;
 | 
				
			||||||
 | 
					use App\Services\Storage\StorageService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final readonly class StorageCommand
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        private StorageService $storageService,
 | 
				
			||||||
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @throws StorageCommandException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function execute(StorageDto $storageDto): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        foreach ($storageDto->getStorages() as $storage) {
 | 
				
			||||||
 | 
					            $storages = $this->storageService->getStoragesAndValidate($storage['storages'], Morph::DocumentationContent, $storage['documentationContent']->id);
 | 
				
			||||||
 | 
					            if (!$storages->isSuccess()) {
 | 
				
			||||||
 | 
					                throw new StorageCommandException($storages, 'Error when adding a file to storage: ' . $storages->getMessage());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $this->storageService->saveAndDelete($storage['documentationContent'], $storages);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,32 +5,44 @@ namespace App\Services\Site;
 | 
				
			|||||||
use App\Dto\Service\Site\Feedback\Send;
 | 
					use App\Dto\Service\Site\Feedback\Send;
 | 
				
			||||||
use App\Models\Project;
 | 
					use App\Models\Project;
 | 
				
			||||||
use App\Models\User;
 | 
					use App\Models\User;
 | 
				
			||||||
 | 
					use App\Notifications\ReviewAdded;
 | 
				
			||||||
use App\ServiceResults\ServiceResultError;
 | 
					use App\ServiceResults\ServiceResultError;
 | 
				
			||||||
use App\ServiceResults\ServiceResultSuccess;
 | 
					use App\ServiceResults\ServiceResultSuccess;
 | 
				
			||||||
use App\Services\ProjectFeedback\ProjectFeedbackCommandHandler;
 | 
					use App\Services\ProjectFeedback\ProjectFeedbackCommandHandler;
 | 
				
			||||||
use App\Services\Service;
 | 
					use App\Services\Service;
 | 
				
			||||||
use App\Services\WebsiteTranslations;
 | 
					use App\Services\WebsiteTranslations;
 | 
				
			||||||
use Illuminate\Support\Facades\DB;
 | 
					use Illuminate\Support\Facades\DB;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Notification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final class FeedbackService extends Service
 | 
					final class FeedbackService extends Service
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        private readonly ProjectFeedbackCommandHandler $feedbackCommandHandler,
 | 
					        private readonly ProjectFeedbackCommandHandler $feedbackCommandHandler,
 | 
				
			||||||
 | 
					        private readonly bool $isNotifications = false,
 | 
				
			||||||
 | 
					        private readonly ?string $mailNotifications = null,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function send(Send $send, Project $project, WebsiteTranslations $websiteTranslations, ?User $user = null): ServiceResultError | ServiceResultSuccess
 | 
					    public function send(Send $send, Project $project, WebsiteTranslations $websiteTranslations, ?User $user = null): ServiceResultError | ServiceResultSuccess
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            DB::transaction(function () use ($send, $project, $user) {
 | 
					            $feedback = DB::transaction(function () use ($send, $project, $user) {
 | 
				
			||||||
                $data = $this->getDataFeedback($send);
 | 
					                $data = $this->getDataFeedback($send);
 | 
				
			||||||
                $data['user_id'] = $user?->id;
 | 
					                $data['user_id'] = $user?->id;
 | 
				
			||||||
                $this->feedbackCommandHandler->handleStore($project, $data);
 | 
					                return $this->feedbackCommandHandler->handleStore($project, $data);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } catch (\Throwable $e) {
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
            report($e);
 | 
					            report($e);
 | 
				
			||||||
            return $this->errService($websiteTranslations->translate('Server Error'));
 | 
					            return $this->errService($websiteTranslations->translate('Server Error'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if ($this->isNotifications) {
 | 
				
			||||||
 | 
					                Notification::route('mail', $this->mailNotifications)->notify(new ReviewAdded($feedback));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (\Throwable $e) {
 | 
				
			||||||
 | 
					            report($e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->ok($websiteTranslations->translate('site.Message sent successfully'));
 | 
					        return $this->ok($websiteTranslations->translate('site.Message sent successfully'));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace App\View\Components\Volt\Forms;
 | 
					namespace App\View\Components\Volt\Forms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use App\Dto\View\Volt\Form\WysiwygStorageUpload;
 | 
				
			||||||
use Illuminate\Support\Str;
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
use Illuminate\View\View;
 | 
					use Illuminate\View\View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,6 +12,7 @@ final class TextareaWysiwyg extends Form
 | 
				
			|||||||
        private readonly string                $title,
 | 
					        private readonly string                $title,
 | 
				
			||||||
        private readonly string                $name,
 | 
					        private readonly string                $name,
 | 
				
			||||||
        private readonly ?string               $value = '',
 | 
					        private readonly ?string               $value = '',
 | 
				
			||||||
 | 
					        private readonly ?WysiwygStorageUpload $storageUpload = null,
 | 
				
			||||||
    ) { }
 | 
					    ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected function getName(): string
 | 
					    protected function getName(): string
 | 
				
			||||||
@@ -28,6 +30,11 @@ final class TextareaWysiwyg extends Form
 | 
				
			|||||||
        return (string) old($this->getRequestName(), $this->value);
 | 
					        return (string) old($this->getRequestName(), $this->value);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getStorageUpload(): ?WysiwygStorageUpload
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->storageUpload;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritDoc
 | 
					     * @inheritDoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -41,6 +48,7 @@ final class TextareaWysiwyg extends Form
 | 
				
			|||||||
            'name'              => $this->getName(),
 | 
					            'name'              => $this->getName(),
 | 
				
			||||||
            'requestName'       => $this->getRequestName(),
 | 
					            'requestName'       => $this->getRequestName(),
 | 
				
			||||||
            'value'             => $this->getValue(),
 | 
					            'value'             => $this->getValue(),
 | 
				
			||||||
 | 
					            'storageUpload'     => $this->getStorageUpload(),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,15 @@ return [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    'env' => env('APP_ENV', 'production'),
 | 
					    'env' => env('APP_ENV', 'production'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					    |--------------------------------------------------------------------------
 | 
				
			||||||
 | 
					    | Сaptcha
 | 
				
			||||||
 | 
					    |--------------------------------------------------------------------------
 | 
				
			||||||
 | 
					    |
 | 
				
			||||||
 | 
					    | Enables or disables captcha.
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    'captcha' => (bool) env('APP_CAPTCHA', false),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
    |--------------------------------------------------------------------------
 | 
					    |--------------------------------------------------------------------------
 | 
				
			||||||
    | Application Debug Mode
 | 
					    | Application Debug Mode
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								app/application/config/feedback.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/application/config/feedback.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return [
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Enable new review alerts.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    'mail_notifications' => (bool) env('FEEDBACK_MAIL_NOTIFICATIONS', false),
 | 
				
			||||||
 | 
					    'mail_to' => env('FEEDBACK_MAIL_TO', null),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					<?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::table('storage', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->dropIndex(['morph_id']);
 | 
				
			||||||
 | 
					            $table->index(['morph_id', 'updated_at']);
 | 
				
			||||||
 | 
					            $table->index(['deleted_at']);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('storage', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->dropIndex(['morph_id', 'updated_at']);
 | 
				
			||||||
 | 
					            $table->dropIndex(['deleted_at']);
 | 
				
			||||||
 | 
					            $table->index(['morph_id']);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -267,5 +267,6 @@
 | 
				
			|||||||
    "Documentation successfully removed": "Documentation successfully removed",
 | 
					    "Documentation successfully removed": "Documentation successfully removed",
 | 
				
			||||||
    "Category successfully created": "Category successfully created",
 | 
					    "Category successfully created": "Category successfully created",
 | 
				
			||||||
    "Category updated successfully": "Category updated successfully",
 | 
					    "Category updated successfully": "Category updated successfully",
 | 
				
			||||||
    "Category successfully deleted": "Category successfully deleted"
 | 
					    "Category successfully deleted": "Category successfully deleted",
 | 
				
			||||||
 | 
					    "Old Files deleted": "Old Files deleted"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								app/application/lang/en/notification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/application/lang/en/notification.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					return [
 | 
				
			||||||
 | 
					    'Added a new review.'  => 'Added a new review.',
 | 
				
			||||||
 | 
					    'Review Added: :name' => 'Review Added: :name',
 | 
				
			||||||
 | 
					    'Project :name' => 'Project :name',
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@@ -307,5 +307,6 @@ return [
 | 
				
			|||||||
        'content.*.title'          => 'title',
 | 
					        'content.*.title'          => 'title',
 | 
				
			||||||
        'content.*.content'        => 'content',
 | 
					        'content.*.content'        => 'content',
 | 
				
			||||||
        'category_id'              => 'category',
 | 
					        'category_id'              => 'category',
 | 
				
			||||||
 | 
					        'content_images'           => 'content images',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -267,5 +267,6 @@
 | 
				
			|||||||
    "Documentation successfully removed": "Документация успешно удалена",
 | 
					    "Documentation successfully removed": "Документация успешно удалена",
 | 
				
			||||||
    "Category successfully created": "Категория успешно создана",
 | 
					    "Category successfully created": "Категория успешно создана",
 | 
				
			||||||
    "Category updated successfully": "Категория успешно обновлена",
 | 
					    "Category updated successfully": "Категория успешно обновлена",
 | 
				
			||||||
    "Category successfully deleted": "Категория успешно удалена"
 | 
					    "Category successfully deleted": "Категория успешно удалена",
 | 
				
			||||||
 | 
					    "Old Files deleted": "Старые файлы удалены"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								app/application/lang/ru/notification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/application/lang/ru/notification.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					return [
 | 
				
			||||||
 | 
					    'Added a new review.'  => 'Добавили новый отзыв.',
 | 
				
			||||||
 | 
					    'Review Added: :name' => 'Отзыв добавлен: :name',
 | 
				
			||||||
 | 
					    'Project :name' => 'Проект :name',
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@@ -307,5 +307,6 @@ return [
 | 
				
			|||||||
        'content.*.title'          => 'заголовок',
 | 
					        'content.*.title'          => 'заголовок',
 | 
				
			||||||
        'content.*.content'        => 'контент',
 | 
					        'content.*.content'        => 'контент',
 | 
				
			||||||
        'category_id'              => 'категория',
 | 
					        'category_id'              => 'категория',
 | 
				
			||||||
 | 
					        'content_images'           => 'изображения контента',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								app/application/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								app/application/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -5,6 +5,7 @@
 | 
				
			|||||||
    "packages": {
 | 
					    "packages": {
 | 
				
			||||||
        "": {
 | 
					        "": {
 | 
				
			||||||
            "dependencies": {
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@fancyapps/ui": "^5.0.36",
 | 
				
			||||||
                "@popperjs/core": "^2.9.2",
 | 
					                "@popperjs/core": "^2.9.2",
 | 
				
			||||||
                "bootstrap": "5.0.2",
 | 
					                "bootstrap": "5.0.2",
 | 
				
			||||||
                "chartist": "^0.11.4",
 | 
					                "chartist": "^0.11.4",
 | 
				
			||||||
@@ -397,6 +398,11 @@
 | 
				
			|||||||
                "node": ">=12"
 | 
					                "node": ">=12"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@fancyapps/ui": {
 | 
				
			||||||
 | 
					            "version": "5.0.36",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@fancyapps/ui/-/ui-5.0.36.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-GMygQzp1MBTFNTT6AzpbL6pXTD6bTxwjmmpI1fe8Ozmmiseu8/g82Sudl1YhcbZmS4bJgaBOF5THDFGpXQ1fDw=="
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "node_modules/@jridgewell/gen-mapping": {
 | 
					        "node_modules/@jridgewell/gen-mapping": {
 | 
				
			||||||
            "version": "0.3.5",
 | 
					            "version": "0.3.5",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@
 | 
				
			|||||||
        "build": "vite build"
 | 
					        "build": "vite build"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
 | 
					        "@fancyapps/ui": "^5.0.36",
 | 
				
			||||||
        "@popperjs/core": "^2.9.2",
 | 
					        "@popperjs/core": "^2.9.2",
 | 
				
			||||||
        "bootstrap": "5.0.2",
 | 
					        "bootstrap": "5.0.2",
 | 
				
			||||||
        "chartist": "^0.11.4",
 | 
					        "chartist": "^0.11.4",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								app/application/resources/fancybox/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/application/resources/fancybox/app.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { Fancybox } from "@fancyapps/ui";
 | 
				
			||||||
 | 
					import "@fancyapps/ui/dist/fancybox/fancybox.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Fancybox.bind('a.image-open', {
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -18,6 +18,7 @@ import "prismjs/components/prism-nginx.js";
 | 
				
			|||||||
import "prismjs/components/prism-docker.js";
 | 
					import "prismjs/components/prism-docker.js";
 | 
				
			||||||
import "prismjs/components/prism-diff.js";
 | 
					import "prismjs/components/prism-diff.js";
 | 
				
			||||||
import "prismjs/components/prism-php.js";
 | 
					import "prismjs/components/prism-php.js";
 | 
				
			||||||
 | 
					import "prismjs/components/prism-yaml.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "prismjs/plugins/toolbar/prism-toolbar.css";
 | 
					import "prismjs/plugins/toolbar/prism-toolbar.css";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
let blockDocumentationVersion = document.querySelector('#documentation-version');
 | 
					let blockDocumentationVersion = document.querySelector('#documentation-version');
 | 
				
			||||||
 | 
					if (blockDocumentationVersion) {
 | 
				
			||||||
    blockDocumentationVersion.querySelector('.documentation-version__button').addEventListener('click', (e) => {
 | 
					    blockDocumentationVersion.querySelector('.documentation-version__button').addEventListener('click', (e) => {
 | 
				
			||||||
        if (blockDocumentationVersion.classList.contains('active')) {
 | 
					        if (blockDocumentationVersion.classList.contains('active')) {
 | 
				
			||||||
            blockDocumentationVersion.classList.remove('active');
 | 
					            blockDocumentationVersion.classList.remove('active');
 | 
				
			||||||
@@ -6,3 +7,4 @@ blockDocumentationVersion.querySelector('.documentation-version__button').addEve
 | 
				
			|||||||
            blockDocumentationVersion.classList.add('active');
 | 
					            blockDocumentationVersion.classList.add('active');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,39 @@ body {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        .content {
 | 
					        .content {
 | 
				
			||||||
            flex: 1 0 auto;
 | 
					            flex: 1 0 auto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            img {
 | 
				
			||||||
 | 
					                max-width: 100%;
 | 
				
			||||||
 | 
					                object-fit: contain;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .image-open {
 | 
				
			||||||
 | 
					                display: block;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                img {
 | 
				
			||||||
 | 
					                    max-width: 100%;
 | 
				
			||||||
 | 
					                    height: auto;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @supports (--custom:property) {
 | 
				
			||||||
 | 
					                [style*="--aspect-ratio"] {
 | 
				
			||||||
 | 
					                    position: relative;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                [style*="--aspect-ratio"]::before {
 | 
				
			||||||
 | 
					                    content: "";
 | 
				
			||||||
 | 
					                    display: block;
 | 
				
			||||||
 | 
					                    padding-bottom: calc(100% / (var(--aspect-ratio)));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                [style*="--aspect-ratio"] img {
 | 
				
			||||||
 | 
					                    position: absolute;
 | 
				
			||||||
 | 
					                    top: 0;
 | 
				
			||||||
 | 
					                    left: 0;
 | 
				
			||||||
 | 
					                    height: 100%;
 | 
				
			||||||
 | 
					                    max-width: 100%;
 | 
				
			||||||
 | 
					                    object-fit: cover;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .footer {
 | 
					        .footer {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1514
									
								
								app/application/resources/tinymce/plugins/my-image/plugin.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1514
									
								
								app/application/resources/tinymce/plugins/my-image/plugin.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1
									
								
								app/application/resources/views/_fancybox.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/application/resources/views/_fancybox.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					@vite('resources/fancybox/app.js')
 | 
				
			||||||
							
								
								
									
										1
									
								
								app/application/resources/views/_tinymce.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/application/resources/views/_tinymce.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<script src="{{ asset('/build/tinymce/tinymce.min.js') }}" referrerpolicy="origin"></script>
 | 
				
			||||||
@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					@pushOnce('scripts')
 | 
				
			||||||
 | 
					    @include('_tinymce')
 | 
				
			||||||
 | 
					    @include('_prism')
 | 
				
			||||||
 | 
					@endpushonce
 | 
				
			||||||
@@ -1,6 +1,11 @@
 | 
				
			|||||||
@csrf
 | 
					@csrf
 | 
				
			||||||
<x-volt.forms.input :title="__('validation.attributes.title')" name="title" type="text" :value="$content->title" required autofocus />
 | 
					<x-volt.forms.input :title="__('validation.attributes.title')" name="title" type="text" :value="$content->title" required autofocus />
 | 
				
			||||||
<x-volt.forms.textarea-wysiwyg :title="__('validation.attributes.description')" name="description" :value="$content->description" />
 | 
					<x-volt.forms.textarea-wysiwyg
 | 
				
			||||||
 | 
					    :title="__('validation.attributes.description')"
 | 
				
			||||||
 | 
					    name="description"
 | 
				
			||||||
 | 
					    :value="$content->description"
 | 
				
			||||||
 | 
					    :storageUpload="new \App\Dto\View\Volt\Form\WysiwygStorageUpload(inputName: 'storage', morph: \App\Enums\Morph::ProjectContent)"
 | 
				
			||||||
 | 
					/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@canany(['create', 'update'], $content)
 | 
					@canany(['create', 'update'], $content)
 | 
				
			||||||
    <button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
 | 
					    <button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,22 @@
 | 
				
			|||||||
@csrf
 | 
					@csrf
 | 
				
			||||||
<x-volt.forms.checkbox :title="__('validation.attributes.is_public')" name="is_public" checkboxValue="1" notCheckedValue="0" :userValue="(string) $documentation->is_public" />
 | 
					<x-volt.forms.checkbox :title="__('validation.attributes.is_public')" name="is_public" checkboxValue="1"
 | 
				
			||||||
<x-volt.forms.input :title="__('validation.attributes.slug')" allowed-characters="a-z0-9.-_" name="slug" type="text" :value="$documentation->slug" required autofocus />
 | 
					                       notCheckedValue="0" :userValue="(string) $documentation->is_public"/>
 | 
				
			||||||
<x-volt.forms.input :title="__('validation.attributes.sort')" name="sort" type="number" :value="$documentation->sort" required />
 | 
					<x-volt.forms.input :title="__('validation.attributes.slug')" allowed-characters="a-z0-9.-_" name="slug" type="text"
 | 
				
			||||||
<x-volt.forms.select :title="__('validation.attributes.category_id')" name="category_id" :list="$categories" :value="(string) $documentation->category?->id">
 | 
					                    :value="$documentation->slug" required autofocus/>
 | 
				
			||||||
 | 
					<x-volt.forms.input :title="__('validation.attributes.sort')" name="sort" type="number" :value="$documentation->sort"
 | 
				
			||||||
 | 
					                    required/>
 | 
				
			||||||
 | 
					<x-volt.forms.select :title="__('validation.attributes.category_id')" name="category_id" :list="$categories"
 | 
				
			||||||
 | 
					                     :value="(string) $documentation->category?->id">
 | 
				
			||||||
    <option value=""></option>
 | 
					    <option value=""></option>
 | 
				
			||||||
</x-volt.forms.select>
 | 
					</x-volt.forms.select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<nav>
 | 
					<nav>
 | 
				
			||||||
    <div class="nav nav-tabs mb-4" id="nav-language-tab" role="tablist">
 | 
					    <div class="nav nav-tabs mb-4" id="nav-language-tab" role="tablist">
 | 
				
			||||||
        @foreach($project->languages as $index => $language)
 | 
					        @foreach($project->languages as $index => $language)
 | 
				
			||||||
            <a class="nav-item nav-link @if($index === 0) active @endif" id="language-{{ $language->id }}-tab" data-bs-toggle="tab" href="#language-{{ $language->id }}" role="tab" aria-controls="language-{{ $language->id }}" aria-selected="{{ $index ? 'false' : 'true' }}">{{ $language->title }}</a>
 | 
					            <a class="nav-item nav-link @if($index === 0) active @endif" id="language-{{ $language->id }}-tab"
 | 
				
			||||||
 | 
					               data-bs-toggle="tab" href="#language-{{ $language->id }}" role="tab"
 | 
				
			||||||
 | 
					               aria-controls="language-{{ $language->id }}"
 | 
				
			||||||
 | 
					               aria-selected="{{ $index ? 'false' : 'true' }}">{{ $language->title }}</a>
 | 
				
			||||||
        @endforeach
 | 
					        @endforeach
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</nav>
 | 
					</nav>
 | 
				
			||||||
@@ -18,10 +25,22 @@
 | 
				
			|||||||
        @php
 | 
					        @php
 | 
				
			||||||
            $content = $documentation->contents->firstWhere('language_id', $language->id);
 | 
					            $content = $documentation->contents->firstWhere('language_id', $language->id);
 | 
				
			||||||
        @endphp
 | 
					        @endphp
 | 
				
			||||||
        <div class="tab-pane fade @if($index === 0) show active @endif" id="language-{{ $language->id }}" role="tabpanel" aria-labelledby="language-{{ $language->id }}-tab">
 | 
					        <div class="tab-pane fade @if($index === 0) show active @endif" id="language-{{ $language->id }}"
 | 
				
			||||||
            <x-volt.forms.checkbox :title="__('Edit')" :name="'content-enable-' . $language->id" :user-value="($index === 0) ? 1 : 0" class="content-enable" checkbox-value="1" notCheckedValue="0"/>
 | 
					             role="tabpanel" aria-labelledby="language-{{ $language->id }}-tab">
 | 
				
			||||||
            <x-volt.forms.input :title="__('validation.attributes.title')" :name="'content[' . $language->id . '][title]'" type="text" class="language-content" :disabled="$index !== 0" :value="$content?->title ?? ''" required />
 | 
					            <x-volt.forms.checkbox :title="__('Edit')" :name="'content-enable-' . $language->id"
 | 
				
			||||||
            <x-volt.forms.textarea-wysiwyg :title="__('validation.attributes.content')" :name="'content[' . $language->id . '][content]'" class="language-content" :value="$content?->content" :disabled="$index !== 0" />
 | 
					                                   :user-value="($index === 0) ? 1 : 0" class="content-enable" checkbox-value="1"
 | 
				
			||||||
 | 
					                                   notCheckedValue="0"/>
 | 
				
			||||||
 | 
					            <x-volt.forms.input :title="__('validation.attributes.title')"
 | 
				
			||||||
 | 
					                                :name="'content[' . $language->id . '][title]'" type="text" class="language-content"
 | 
				
			||||||
 | 
					                                :disabled="$index !== 0" :value="$content?->title ?? ''" required/>
 | 
				
			||||||
 | 
					            <x-volt.forms.textarea-wysiwyg
 | 
				
			||||||
 | 
					                :title="__('validation.attributes.content')"
 | 
				
			||||||
 | 
					                :storageUpload="new \App\Dto\View\Volt\Form\WysiwygStorageUpload(inputName: 'content[' . $language->id . ']', morph: \App\Enums\Morph::DocumentationContent)"
 | 
				
			||||||
 | 
					                :name="'content[' . $language->id . '][content]'"
 | 
				
			||||||
 | 
					                class="language-content"
 | 
				
			||||||
 | 
					                :value="$content?->content"
 | 
				
			||||||
 | 
					                :disabled="$index !== 0"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    @endforeach
 | 
					    @endforeach
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +1,114 @@
 | 
				
			|||||||
 | 
					@php
 | 
				
			||||||
 | 
					    $tinyId = \Illuminate\Support\Str::random(7);
 | 
				
			||||||
 | 
					    $tinyId = 'form-textarea-wysiwyg-' . $tinyId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $images = [];
 | 
				
			||||||
 | 
					    $files  = [];
 | 
				
			||||||
 | 
					    /** @var \App\Dto\View\Volt\Form\WysiwygStorageUpload | null $storageUpload */
 | 
				
			||||||
 | 
					    if ($storageUpload !== null) {
 | 
				
			||||||
 | 
					        $storages = old($storageUpload->getRequestInputName(), []);
 | 
				
			||||||
 | 
					        $images = $storages['content_images'] ?? [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					@endphp
 | 
				
			||||||
<div class="mb-3">
 | 
					<div class="mb-3">
 | 
				
			||||||
    <label for="form-textarea-wysiwyg-{{ $requestName }}">{{ $title }}</label>
 | 
					    <label for="{{ $tinyId }}">{{ $title }}</label>
 | 
				
			||||||
    <textarea class="form-control {{ $attributes->get('class') }} textarea-tinymce @error($requestName) is-invalid @enderror" name="{{ $name }}" id="form-textarea-wysiwyg-{{ $requestName }}" rows="3">{{ $value }}</textarea>
 | 
					    <textarea class="form-control {{ $attributes->get('class') }} textarea-tinymce @error($requestName) is-invalid @enderror" name="{{ $name }}" id="{{ $tinyId }}" rows="3">{{ $value }}</textarea>
 | 
				
			||||||
 | 
					    @foreach($images as $image)
 | 
				
			||||||
 | 
					        @continue( empty($image['file']) )
 | 
				
			||||||
 | 
					        <input type="hidden" value="{{ $image['file'] }}" name="{{ $storageUpload->getInputName() }}[content_images][][file]';">
 | 
				
			||||||
 | 
					    @endforeach
 | 
				
			||||||
    @error($requestName)
 | 
					    @error($requestName)
 | 
				
			||||||
        <span class="invalid-feedback">{{ $message }}</span>
 | 
					        <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
    @enderror
 | 
					    @enderror
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
@pushOnce('scripts')
 | 
					@include('admin._scripts._tinymce')
 | 
				
			||||||
    <script src="{{ asset('/build/tinymce/tinymce.min.js') }}" referrerpolicy="origin"></script>
 | 
					@push('scripts')
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        document.addEventListener('DOMContentLoaded', () => {
 | 
					        document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
 | 
					            @if($storageUpload !== null)
 | 
				
			||||||
 | 
					                const textarea = document.querySelector('#{{ $tinyId }}');
 | 
				
			||||||
 | 
					                if (!textarea) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const storageImageType = '{{ \App\Enums\StorageType::ContentImages->value }}';
 | 
				
			||||||
 | 
					                const morph = '{{ $storageUpload->getMorph()->value }}';
 | 
				
			||||||
 | 
					                const storageInputName = '{{ $storageUpload->getInputName() }}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const imageUpload = (blobInfo, progress) => new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					                    const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					                    xhr.withCredentials = false;
 | 
				
			||||||
 | 
					                    xhr.open('POST', '{{ route('storage.image_upload_and_resize') }}');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    xhr.upload.onprogress = (e) => {
 | 
				
			||||||
 | 
					                        progress(e.loaded / e.total * 100);
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    xhr.onload = () => {
 | 
				
			||||||
 | 
					                        if (xhr.status === 422) {
 | 
				
			||||||
 | 
					                            const json = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					                            let error = 'Error:<br><br>';
 | 
				
			||||||
 | 
					                            for (let key of Object.keys(json.errors)) {
 | 
				
			||||||
 | 
					                                error += json.errors[key] + '<br><br>';
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            reject(error);
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (xhr.status === 403) {
 | 
				
			||||||
 | 
					                            reject({ message: 'HTTP Error: ' + xhr.status, remove: true });
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (xhr.status < 200 || xhr.status >= 300) {
 | 
				
			||||||
 | 
					                            reject('HTTP Error: ' + xhr.status);
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        const json = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (!json || typeof json.url != 'string') {
 | 
				
			||||||
 | 
					                            reject('Invalid JSON: ' + xhr.responseText);
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        let input = document.createElement('input');
 | 
				
			||||||
 | 
					                        input.type = 'hidden';
 | 
				
			||||||
 | 
					                        input.name = storageInputName + '[content_images][][file]';
 | 
				
			||||||
 | 
					                        input.value = json.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        textarea.after(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        resolve(json.url);
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    xhr.onerror = () => {
 | 
				
			||||||
 | 
					                        reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const formData = new FormData();
 | 
				
			||||||
 | 
					                    formData.append('file', blobInfo.blob(), blobInfo.filename());
 | 
				
			||||||
 | 
					                    formData.append('_token', document.querySelector('meta[name="csrf-token"]').content);
 | 
				
			||||||
 | 
					                    formData.append('storage_type', storageImageType);
 | 
				
			||||||
 | 
					                    formData.append('morph', morph);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    xhr.send(formData);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            @endif
 | 
				
			||||||
            tinymce.init({
 | 
					            tinymce.init({
 | 
				
			||||||
                selector: '.textarea-tinymce',
 | 
					                selector: '#{{ $tinyId }}',
 | 
				
			||||||
                @if(in_array(app()->getLocale(), ['ru'], true))
 | 
					                @if(in_array(app()->getLocale(), ['ru'], true))
 | 
				
			||||||
                    language: '{{ app()->getLocale() }}',
 | 
					                    language: '{{ app()->getLocale() }}',
 | 
				
			||||||
                @endif
 | 
					                @endif
 | 
				
			||||||
                license_key: '{{ $tinymceLicenseKey }}',
 | 
					                license_key: '{{ $tinymceLicenseKey }}',
 | 
				
			||||||
                plugins: 'advlist code emoticons link lists table codesample',
 | 
					                plugins: 'advlist code emoticons link lists table codesample media my-image',
 | 
				
			||||||
                toolbar: 'bold italic | bullist numlist | link emoticons codesample',
 | 
					                toolbar: 'bold italic | bullist numlist | link image emoticons media codesample',
 | 
				
			||||||
                referrer_policy: 'origin',
 | 
					                referrer_policy: 'origin',
 | 
				
			||||||
 | 
					                @if($storageUpload !== null)
 | 
				
			||||||
 | 
					                    images_upload_handler: imageUpload,
 | 
				
			||||||
 | 
					                @endif
 | 
				
			||||||
 | 
					                relative_urls: false,
 | 
				
			||||||
 | 
					                convert_urls: false,
 | 
				
			||||||
                codesample_global_prismjs: true,
 | 
					                codesample_global_prismjs: true,
 | 
				
			||||||
                codesample_languages: [
 | 
					                codesample_languages: [
 | 
				
			||||||
                    {text: 'HTML/XML', value: 'markup'},
 | 
					                    {text: 'HTML/XML', value: 'markup'},
 | 
				
			||||||
@@ -33,11 +124,11 @@
 | 
				
			|||||||
                    {text: 'Go', value: 'go'},
 | 
					                    {text: 'Go', value: 'go'},
 | 
				
			||||||
                    {text: 'Nginx', value: 'nginx'},
 | 
					                    {text: 'Nginx', value: 'nginx'},
 | 
				
			||||||
                    {text: 'Docker', value: 'docker'},
 | 
					                    {text: 'Docker', value: 'docker'},
 | 
				
			||||||
 | 
					                    {text: 'Yaml', value: 'yaml'},
 | 
				
			||||||
                    {text: "Treeview", value: "treeview"},
 | 
					                    {text: "Treeview", value: "treeview"},
 | 
				
			||||||
                    {text: "Diff", value: "diff"},
 | 
					                    {text: "Diff", value: "diff"},
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
    @include('_prism')
 | 
					@endpush
 | 
				
			||||||
@endpushonce
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@
 | 
				
			|||||||
        </header>
 | 
					        </header>
 | 
				
			||||||
        <div class="main-container">
 | 
					        <div class="main-container">
 | 
				
			||||||
            <nav id="menu">
 | 
					            <nav id="menu">
 | 
				
			||||||
                <div class="menu__title">{{ __('site.Menu') }}</div>
 | 
					                <div class="menu__title">{{ $websiteTranslations->translate('site.Menu') }}</div>
 | 
				
			||||||
                <ul>
 | 
					                <ul>
 | 
				
			||||||
                    <li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['home', 'language.home', 'project.home', 'project.language.home'])])>{{ $websiteTranslations->translate('site.About project') }}</a></li>
 | 
					                    <li><a href="{{ \App\Enums\Site\ProjectSection::Home->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['home', 'language.home', 'project.home', 'project.language.home'])])>{{ $websiteTranslations->translate('site.About project') }}</a></li>
 | 
				
			||||||
                    <li><a href="{{ \App\Enums\Site\ProjectSection::Documentation->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['documentation', 'documentation.home'])])>{{ $websiteTranslations->translate('site.Documentation') }}</a></li>
 | 
					                    <li><a href="{{ \App\Enums\Site\ProjectSection::Documentation->url($project, $websiteTranslations->getLanguage()) }}" @class(['active' => request()->route()->named(['documentation', 'documentation.home'])])>{{ $websiteTranslations->translate('site.Documentation') }}</a></li>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,9 +49,11 @@
 | 
				
			|||||||
                                </label>
 | 
					                                </label>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        @if($captcha)
 | 
				
			||||||
                            <div class="form-group mb-4">
 | 
					                            <div class="form-group mb-4">
 | 
				
			||||||
                                @captcha
 | 
					                                @captcha
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        @endif
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="d-grid">
 | 
					                    <div class="d-grid">
 | 
				
			||||||
                        <button type="submit" class="btn btn-gray-800">{{ __('Sign in') }}</button>
 | 
					                        <button type="submit" class="btn btn-gray-800">{{ __('Sign in') }}</button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,14 @@
 | 
				
			|||||||
            <x-site.forms.input :title="$websiteTranslations->translate('site.attributes.name')" :websiteTranslations="$websiteTranslations" name="name" type="text" value="" autofocus />
 | 
					            <x-site.forms.input :title="$websiteTranslations->translate('site.attributes.name')" :websiteTranslations="$websiteTranslations" name="name" type="text" value="" autofocus />
 | 
				
			||||||
            <x-site.forms.input :title="$websiteTranslations->translate('site.attributes.email')" :websiteTranslations="$websiteTranslations" name="email" type="text" value="" />
 | 
					            <x-site.forms.input :title="$websiteTranslations->translate('site.attributes.email')" :websiteTranslations="$websiteTranslations" name="email" type="text" value="" />
 | 
				
			||||||
            <x-site.forms.textarea-wysiwyg :title="$websiteTranslations->translate('site.attributes.message')" :websiteTranslations="$websiteTranslations" required name="message" value="" />
 | 
					            <x-site.forms.textarea-wysiwyg :title="$websiteTranslations->translate('site.attributes.message')" :websiteTranslations="$websiteTranslations" required name="message" value="" />
 | 
				
			||||||
 | 
					            @if($captcha)
 | 
				
			||||||
                <div class="form-block">
 | 
					                <div class="form-block">
 | 
				
			||||||
                    @captcha
 | 
					                    @captcha
 | 
				
			||||||
                    @error('captcha-verified')
 | 
					                    @error('captcha-verified')
 | 
				
			||||||
                        <span class="invalid-feedback">{{ $message }}</span>
 | 
					                        <span class="invalid-feedback">{{ $message }}</span>
 | 
				
			||||||
                    @enderror
 | 
					                    @enderror
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					            @endif
 | 
				
			||||||
            <div class="form-block">
 | 
					            <div class="form-block">
 | 
				
			||||||
                <button class="button" type="submit">{{ $websiteTranslations->translate('site.Feedback-send') }}</button>
 | 
					                <button class="button" type="submit">{{ $websiteTranslations->translate('site.Feedback-send') }}</button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,5 +11,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @push('scripts')
 | 
					    @push('scripts')
 | 
				
			||||||
        @include('_prism')
 | 
					        @include('_prism')
 | 
				
			||||||
 | 
					        @include('_fancybox')
 | 
				
			||||||
    @endpush
 | 
					    @endpush
 | 
				
			||||||
</x-site.layout>
 | 
					</x-site.layout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,5 +5,6 @@
 | 
				
			|||||||
    <div class="line-numbers">{!! $documentation->content->content !!}</div>
 | 
					    <div class="line-numbers">{!! $documentation->content->content !!}</div>
 | 
				
			||||||
    @push('scripts')
 | 
					    @push('scripts')
 | 
				
			||||||
        @include('_prism')
 | 
					        @include('_prism')
 | 
				
			||||||
 | 
					        @include('_fancybox')
 | 
				
			||||||
    @endpush
 | 
					    @endpush
 | 
				
			||||||
</x-site.layout>
 | 
					</x-site.layout>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,2 +1,6 @@
 | 
				
			|||||||
<?php
 | 
					<?php
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schedule;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$timezone = config('app.user_timezone');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Schedule::command(\App\Console\Commands\Files\DeleteOldFilesFromStorage::class)->timezone($timezone)->dailyAt('3:30');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,8 @@ export default defineConfig({
 | 
				
			|||||||
                'resources/site/js/app.js',
 | 
					                'resources/site/js/app.js',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                'resources/prism/app.js',
 | 
					                'resources/prism/app.js',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                'resources/fancybox/app.js',
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            refresh: true,
 | 
					            refresh: true,
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,10 +64,12 @@ FROM BUILD AS PRODUCTION
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
COPY --from=APP_BUILD_FOR_PRODUCTION /home/app /var/www/html
 | 
					COPY --from=APP_BUILD_FOR_PRODUCTION /home/app /var/www/html
 | 
				
			||||||
COPY docker/docker-entrypoint_prod.sh /home/unit/docker-entrypoint.sh
 | 
					COPY docker/docker-entrypoint_prod.sh /home/unit/docker-entrypoint.sh
 | 
				
			||||||
 | 
					COPY docker/start.sh /usr/local/bin/start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /var/www/html
 | 
					WORKDIR /var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN chmod 755 /home/unit/docker-entrypoint.sh
 | 
					RUN chmod 755 /home/unit/docker-entrypoint.sh \
 | 
				
			||||||
 | 
					    && chmod 755 /usr/local/bin/start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
STOPSIGNAL SIGTERM
 | 
					STOPSIGNAL SIGTERM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,17 +83,19 @@ FROM BUILD AS DEVELOP
 | 
				
			|||||||
WORKDIR /var/www/html
 | 
					WORKDIR /var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY docker/docker-entrypoint_dev.sh /home/unit/docker-entrypoint.sh
 | 
					COPY docker/docker-entrypoint_dev.sh /home/unit/docker-entrypoint.sh
 | 
				
			||||||
 | 
					COPY docker/start.sh /usr/local/bin/start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
STOPSIGNAL SIGTERM
 | 
					STOPSIGNAL SIGTERM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN chmod 755 /home/unit/docker-entrypoint.sh \
 | 
					RUN chmod 755 /home/unit/docker-entrypoint.sh \
 | 
				
			||||||
 | 
					    && chmod 755 /usr/local/bin/start \
 | 
				
			||||||
    && apk --no-cache add git nodejs npm \
 | 
					    && apk --no-cache add git nodejs npm \
 | 
				
			||||||
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
 | 
					    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
 | 
					ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXPOSE 9000
 | 
					EXPOSE 9000
 | 
				
			||||||
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
 | 
					CMD ["/usr/local/bin/start"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								app/docker/start.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								app/docker/start.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env sh
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					role=${CONTAINER_ROLE:-app}
 | 
				
			||||||
 | 
					if [ "$role" = "app" ]; then
 | 
				
			||||||
 | 
					    exec unitd --no-daemon --control unix:/var/run/control.unit.sock --user unit --group unit
 | 
				
			||||||
 | 
					elif [ "$role" = "queue" ]; then
 | 
				
			||||||
 | 
					    echo "Running the queue..."
 | 
				
			||||||
 | 
					    while [ true ]
 | 
				
			||||||
 | 
					    do
 | 
				
			||||||
 | 
					      php /var/www/html/artisan queue:work --verbose --sleep=5 --tries=100 --backoff=10 --max-time=3600 --queue=high,normal,low,default
 | 
				
			||||||
 | 
					    done
 | 
				
			||||||
 | 
					elif [ "$role" = "scheduler" ]; then
 | 
				
			||||||
 | 
					    while [ true ]
 | 
				
			||||||
 | 
					    do
 | 
				
			||||||
 | 
					      php /var/www/html/artisan schedule:run --verbose --no-interaction &
 | 
				
			||||||
 | 
					      sleep 60
 | 
				
			||||||
 | 
					    done
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    echo "Could not match the container role \"$role\""
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
@@ -16,9 +16,39 @@ services:
 | 
				
			|||||||
      - ./app/application:/var/www/html
 | 
					      - ./app/application:/var/www/html
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: app
 | 
					      CONTAINER_ROLE: app
 | 
				
			||||||
      UNIT_SOURCE: '["172.16.0.0/12"]'
 | 
					      UNIT_SOURCE: '"172.16.0.0/12"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  queue:
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: app
 | 
				
			||||||
 | 
					      dockerfile: docker/Dockerfile
 | 
				
			||||||
 | 
					      target: PRODUCTION
 | 
				
			||||||
 | 
					    #  restart: always
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - db
 | 
				
			||||||
 | 
					      - app-redis
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER_ROLE: queue
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app/application:/var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  scheduler:
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: app
 | 
				
			||||||
 | 
					      dockerfile: docker/Dockerfile
 | 
				
			||||||
 | 
					      target: PRODUCTION
 | 
				
			||||||
 | 
					    #  restart: always
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - db
 | 
				
			||||||
 | 
					      - app-redis
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER_ROLE: scheduler
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app/application:/var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  app-redis:
 | 
					  app-redis:
 | 
				
			||||||
    image: redis:3.0-alpine
 | 
					    image: redis:3.0-alpine # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./redis/data:/data
 | 
					      - ./redis/data:/data
 | 
				
			||||||
@@ -39,7 +69,7 @@ services:
 | 
				
			|||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: app
 | 
					      CONTAINER_ROLE: app
 | 
				
			||||||
      UNIT_SOURCE: '["172.16.0.0/12"]'
 | 
					      UNIT_SOURCE: '"172.16.0.0/12"'
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${DOCKER_CAPTCHA_PORT}:9000
 | 
					      - ${DOCKER_CAPTCHA_PORT}:9000
 | 
				
			||||||
  captcha-queue:
 | 
					  captcha-queue:
 | 
				
			||||||
@@ -72,12 +102,14 @@ services:
 | 
				
			|||||||
      CONTAINER_ROLE: scheduler
 | 
					      CONTAINER_ROLE: scheduler
 | 
				
			||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
  captcha-redis:
 | 
					  captcha-redis:
 | 
				
			||||||
    image: redis:3.0-alpine
 | 
					    image: redis:3.0-alpine # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./captcha-app/redis/data:/data
 | 
					      - ./captcha-app/redis/data:/data
 | 
				
			||||||
  db:
 | 
					  db:
 | 
				
			||||||
    image: docker.io/mysql:8.0.33
 | 
					    image: docker.io/mysql:8.0.33 # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/mysql:8.0.33 # MDHub
 | 
				
			||||||
    command: --default-authentication-plugin=mysql_native_password
 | 
					    command: --default-authentication-plugin=mysql_native_password
 | 
				
			||||||
    #restart: always
 | 
					    #restart: always
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
version: '3.7'
 | 
					version: '3.7'
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  app:
 | 
					  app:
 | 
				
			||||||
    image: korelf/my-projects-website:0.2.1
 | 
					    #  image: korelf/my-projects-website:0.3.0 # docker hub
 | 
				
			||||||
 | 
					    image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.3.0 # MDHub
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - db
 | 
					      - db
 | 
				
			||||||
@@ -12,12 +13,42 @@ services:
 | 
				
			|||||||
    env_file: app/.env
 | 
					    env_file: app/.env
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: app
 | 
					      CONTAINER_ROLE: app
 | 
				
			||||||
      UNIT_SOURCE: '["172.16.0.0/12"]'
 | 
					      UNIT_SOURCE: '"172.16.0.0/12"'
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./app/storage/app:/var/www/html/storage/app
 | 
					      - ./app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
      - ./app/storage/logs:/var/www/html/storage/logs
 | 
					      - ./app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  queue:
 | 
				
			||||||
 | 
					    #  image: korelf/my-projects-website:0.3.0 # docker hub
 | 
				
			||||||
 | 
					    image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.3.0 # MDHub
 | 
				
			||||||
 | 
					    #  restart: always
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - db
 | 
				
			||||||
 | 
					      - app-redis
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER_ROLE: queue
 | 
				
			||||||
 | 
					    env_file: app/.env
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
 | 
					      - ./app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  scheduler:
 | 
				
			||||||
 | 
					    #  image: korelf/my-projects-website:0.3.0 # docker hub
 | 
				
			||||||
 | 
					    image: docker.mdhub.kor-elf.net/kor-elf/my-projects-website:0.3.0 # MDHub
 | 
				
			||||||
 | 
					    #  restart: always
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - db
 | 
				
			||||||
 | 
					      - app-redis
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER_ROLE: scheduler
 | 
				
			||||||
 | 
					    env_file: app/.env
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
 | 
					      - ./app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  app-redis:
 | 
					  app-redis:
 | 
				
			||||||
    image: redis:3.0-alpine
 | 
					    image: redis:3.0-alpine # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./redis/data:/data
 | 
					      - ./redis/data:/data
 | 
				
			||||||
@@ -40,7 +71,7 @@ services:
 | 
				
			|||||||
      - ${DOCKER_CAPTCHA_PORT}:9000
 | 
					      - ${DOCKER_CAPTCHA_PORT}:9000
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: app
 | 
					      CONTAINER_ROLE: app
 | 
				
			||||||
      UNIT_SOURCE: '["172.16.0.0/12"]'
 | 
					      UNIT_SOURCE: '"172.16.0.0/12"'
 | 
				
			||||||
  captcha-queue:
 | 
					  captcha-queue:
 | 
				
			||||||
    image: korelf/service-captcha:0.8.2
 | 
					    image: korelf/service-captcha:0.8.2
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
@@ -71,12 +102,14 @@ services:
 | 
				
			|||||||
      CONTAINER_ROLE: scheduler
 | 
					      CONTAINER_ROLE: scheduler
 | 
				
			||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
  captcha-redis:
 | 
					  captcha-redis:
 | 
				
			||||||
    image: redis:3.0-alpine
 | 
					    image: redis:3.0-alpine # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./captcha-app/redis/data:/data
 | 
					      - ./captcha-app/redis/data:/data
 | 
				
			||||||
  db:
 | 
					  db:
 | 
				
			||||||
    image: docker.io/mysql:8.0.33
 | 
					    image: docker.io/mysql:8.0.33 # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/mysql:8.0.33 # MDHub
 | 
				
			||||||
    command: --default-authentication-plugin=mysql_native_password
 | 
					    command: --default-authentication-plugin=mysql_native_password
 | 
				
			||||||
    #restart: always
 | 
					    #restart: always
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,13 +13,41 @@ services:
 | 
				
			|||||||
      - ${DOCKER_APP_PORT}:9000
 | 
					      - ${DOCKER_APP_PORT}:9000
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./app/application:/var/www/html
 | 
					      - ./app/application:/var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  queue:
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: app
 | 
				
			||||||
 | 
					      dockerfile: docker/Dockerfile
 | 
				
			||||||
 | 
					      target: DEVELOP
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - db
 | 
				
			||||||
 | 
					      - app-redis
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER_ROLE: queue
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app/application:/var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  scheduler:
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: app
 | 
				
			||||||
 | 
					      dockerfile: docker/Dockerfile
 | 
				
			||||||
 | 
					      target: DEVELOP
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - db
 | 
				
			||||||
 | 
					      - app-redis
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER_ROLE: scheduler
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./app/application:/var/www/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  app-redis:
 | 
					  app-redis:
 | 
				
			||||||
    image: redis:3.0-alpine
 | 
					    image: redis:3.0-alpine # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./redis/data:/data
 | 
					      - ./redis/data:/data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  captcha-app:
 | 
					  captcha-app:
 | 
				
			||||||
    image: korelf/service-captcha:0.8.1
 | 
					    image: korelf/service-captcha:0.8.2
 | 
				
			||||||
    cap_drop:
 | 
					    cap_drop:
 | 
				
			||||||
      - ALL
 | 
					      - ALL
 | 
				
			||||||
    cap_add:
 | 
					    cap_add:
 | 
				
			||||||
@@ -31,10 +59,13 @@ services:
 | 
				
			|||||||
      - db
 | 
					      - db
 | 
				
			||||||
      - captcha-redis
 | 
					      - captcha-redis
 | 
				
			||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${DOCKER_CAPTCHA_PORT}:9000
 | 
					      - ${DOCKER_CAPTCHA_PORT}:9000
 | 
				
			||||||
  captcha-queue:
 | 
					  captcha-queue:
 | 
				
			||||||
    image: korelf/service-captcha:0.8.1
 | 
					    image: korelf/service-captcha:0.8.2
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - db
 | 
					      - db
 | 
				
			||||||
@@ -42,8 +73,11 @@ services:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: queue
 | 
					      CONTAINER_ROLE: queue
 | 
				
			||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
  captcha-reverb:
 | 
					  captcha-reverb:
 | 
				
			||||||
    image: korelf/service-captcha:0.8.1
 | 
					    image: korelf/service-captcha:0.8.2
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - db
 | 
					      - db
 | 
				
			||||||
@@ -51,10 +85,13 @@ services:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: websockets
 | 
					      CONTAINER_ROLE: websockets
 | 
				
			||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - ${DOCKER_CAPTCHA_WEBSOCKET_PORT}:9000
 | 
					      - ${DOCKER_CAPTCHA_WEBSOCKET_PORT}:9000
 | 
				
			||||||
  captcha-scheduler:
 | 
					  captcha-scheduler:
 | 
				
			||||||
    image: korelf/service-captcha:0.8.1
 | 
					    image: korelf/service-captcha:0.8.2
 | 
				
			||||||
    #  restart: always
 | 
					    #  restart: always
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - db
 | 
					      - db
 | 
				
			||||||
@@ -62,12 +99,17 @@ services:
 | 
				
			|||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER_ROLE: scheduler
 | 
					      CONTAINER_ROLE: scheduler
 | 
				
			||||||
    env_file: captcha-app/.env
 | 
					    env_file: captcha-app/.env
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/app:/var/www/html/storage/app
 | 
				
			||||||
 | 
					      - ./captcha-app/app/storage/logs:/var/www/html/storage/logs
 | 
				
			||||||
  captcha-redis:
 | 
					  captcha-redis:
 | 
				
			||||||
    image: redis:3.0-alpine
 | 
					    image: redis:3.0-alpine # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/redis:3.0-alpine # MDHub
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./captcha-app/redis/data:/data
 | 
					      - ./captcha-app/redis/data:/data
 | 
				
			||||||
  db:
 | 
					  db:
 | 
				
			||||||
    image: docker.io/mysql:8.0.33
 | 
					    image: docker.io/mysql:8.0.33 # docker hub
 | 
				
			||||||
 | 
					    # image: docker.mdhub.kor-elf.net/kor-elf/mysql:8.0.33 # MDHub
 | 
				
			||||||
    command: --default-authentication-plugin=mysql_native_password
 | 
					    command: --default-authentication-plugin=mysql_native_password
 | 
				
			||||||
    #restart: always
 | 
					    #restart: always
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user