Compare commits
12 Commits
417ce35fc8
...
907a41e057
Author | SHA1 | Date | |
---|---|---|---|
907a41e057 | |||
3902f5d36f | |||
46da639055 | |||
c84ed9f12b | |||
dde792b97a | |||
618c925dfa | |||
8bc4c0d8d0 | |||
a5e3c5ed25 | |||
22bb840705 | |||
707762d29b | |||
b33362a235 | |||
4d36821ecc |
@ -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 @@ public function getDescription(): string
|
|||||||
{
|
{
|
||||||
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 @@ public function getContent(): string
|
|||||||
{
|
{
|
||||||
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,39 +9,43 @@
|
|||||||
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'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAcceptMimes(): array
|
public function getAcceptMimes(): array
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Logo => ['jpeg', 'jpg', 'png'],
|
self::Logo => ['jpeg', 'jpg', 'png'],
|
||||||
|
self::ContentImages => ['jpeg', 'jpg', 'png'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isImage(): bool
|
public function isImage(): bool
|
||||||
{
|
{
|
||||||
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 @@ public function __construct(
|
|||||||
|
|
||||||
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 @@ public function index(Request $request): View
|
|||||||
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 @@
|
|||||||
|
|
||||||
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 @@ public function rules(): array
|
|||||||
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 @@ public function getDto(): StoreUpdate
|
|||||||
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\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 @@ public function rules(): array
|
|||||||
'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 @@ private function getContents(): Contents
|
|||||||
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 @@ public function attributes(): array
|
|||||||
*/
|
*/
|
||||||
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\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 @@ public function getDto(): Upload
|
|||||||
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 @@ public function update(User $user, ProjectContent $projectContent): bool
|
|||||||
{
|
{
|
||||||
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 @@ public function register(): void
|
|||||||
});
|
});
|
||||||
|
|
||||||
$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\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 @@ public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null
|
|||||||
->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 @@ public function getForSelect(?ProjectLanguage $defaultLanguage, ?DocumentationCa
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
$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 @@
|
|||||||
|
|
||||||
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\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 @@ public function __construct(
|
|||||||
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 @@ private function store(int $projectId, int $languageId, StoreUpdate $data, User
|
|||||||
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 @@ private function update(ProjectContent $content, StoreUpdate $data, User $user):
|
|||||||
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 @@ public function create(int $projectId, int $versionId, User $user): ServiceResul
|
|||||||
'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 @@ public function edit(int $projectId, int $versionId, int $categoryId, User $user
|
|||||||
'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 @@
|
|||||||
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 @@ public function create(int $projectId, int $versionId, User $user): ServiceResul
|
|||||||
'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 @@ public function edit(int $projectId, int $versionId, int $documentationId, User
|
|||||||
'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 @@ public function store(int $projectId, int $versionId, StoreUpdate $data, User $u
|
|||||||
|
|
||||||
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 @@ public function update(int $projectId, int $versionId, int $documentationId, Sto
|
|||||||
|
|
||||||
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 @@
|
|||||||
|
|
||||||
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 @@ public function execute(Project $project, Documentation $documentation, Contents
|
|||||||
$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 @@
|
|||||||
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,15 +2,17 @@
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
final class TextareaWysiwyg extends Form
|
final class TextareaWysiwyg extends Form
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
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 @@ private function getValue(): string
|
|||||||
return (string) old($this->getRequestName(), $this->value);
|
return (string) old($this->getRequestName(), $this->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStorageUpload(): ?WysiwygStorageUpload
|
||||||
|
{
|
||||||
|
return $this->storageUpload;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@ -37,10 +44,11 @@ public function render(): View
|
|||||||
|
|
||||||
return view('components.volt.forms.textarea-wysiwyg', [
|
return view('components.volt.forms.textarea-wysiwyg', [
|
||||||
'tinymceLicenseKey' => $tinymceLicenseKey,
|
'tinymceLicenseKey' => $tinymceLicenseKey,
|
||||||
'title' => $this->getTitle(),
|
'title' => $this->getTitle(),
|
||||||
'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 @@
|
|||||||
|
|
||||||
'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 @@
|
|||||||
'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 @@
|
|||||||
'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,8 +1,10 @@
|
|||||||
let blockDocumentationVersion = document.querySelector('#documentation-version');
|
let blockDocumentationVersion = document.querySelector('#documentation-version');
|
||||||
blockDocumentationVersion.querySelector('.documentation-version__button').addEventListener('click', (e) => {
|
if (blockDocumentationVersion) {
|
||||||
if (blockDocumentationVersion.classList.contains('active')) {
|
blockDocumentationVersion.querySelector('.documentation-version__button').addEventListener('click', (e) => {
|
||||||
blockDocumentationVersion.classList.remove('active');
|
if (blockDocumentationVersion.classList.contains('active')) {
|
||||||
} else {
|
blockDocumentationVersion.classList.remove('active');
|
||||||
blockDocumentationVersion.classList.add('active');
|
} else {
|
||||||
}
|
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>
|
||||||
<div class="form-group mb-4">
|
@if($captcha)
|
||||||
@captcha
|
<div class="form-group mb-4">
|
||||||
</div>
|
@captcha
|
||||||
|
</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="" />
|
||||||
<div class="form-block">
|
@if($captcha)
|
||||||
@captcha
|
<div class="form-block">
|
||||||
@error('captcha-verified')
|
@captcha
|
||||||
<span class="invalid-feedback">{{ $message }}</span>
|
@error('captcha-verified')
|
||||||
@enderror
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
</div>
|
@enderror
|
||||||
|
</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:
|
||||||
|
Loading…
Reference in New Issue
Block a user