Added the ability to upload pictures in the editor.

This commit is contained in:
2024-07-26 22:04:39 +05:00
parent 4d36821ecc
commit b33362a235
34 changed files with 2007 additions and 40 deletions

View File

@@ -3,12 +3,14 @@
namespace App\Dto\Service\Admin\Project\About;
use App\Dto\Service\Dto;
use App\Dto\Service\Storage\Storages;
final readonly class StoreUpdate extends Dto
{
public function __construct(
private string $title,
private string $description,
private Storages $storages,
) { }
public function getTitle(): string
@@ -20,4 +22,9 @@ final readonly class StoreUpdate extends Dto
{
return $this->description;
}
public function getStorages(): Storages
{
return $this->storages;
}
}

View File

@@ -2,12 +2,15 @@
namespace App\Dto\Service\Admin\Project\DocumentationContent;
use App\Dto\Service\Storage\Storages;
final readonly class Content
{
public function __construct(
private int $languageId,
private string $title,
private string $content,
private Storages $storages,
) { }
public function getLanguageId(): int
@@ -24,4 +27,9 @@ final readonly class Content
{
return $this->content;
}
public function getStorages(): Storages
{
return $this->storages;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -2,16 +2,22 @@
namespace App\Enums;
use App\Models\DocumentationContent;
use App\Models\Project;
use App\Models\ProjectContent;
enum Morph: int
{
case Project = 1;
case DocumentationContent = 2;
case ProjectContent = 3;
public function getPathModel(): string
{
return match ($this) {
self::Project => Project::class,
self::Project => Project::class,
self::DocumentationContent => DocumentationContent::class,
self::ProjectContent => ProjectContent::class,
};
}

View File

@@ -9,39 +9,43 @@ use App\Contracts\StorageType\Video;
enum StorageType: int implements Image, Video, Audio
{
case Logo = 1;
case ContentImages = 2;
public function getTitle(): string
{
return match ($this) {
self::Logo => __('validation.attributes.logo'),
self::ContentImages => __('validation.attributes.content_images'),
};
}
public function getAcceptMimes(): array
{
return match ($this) {
self::Logo => ['jpeg', 'jpg', 'png'],
self::Logo => ['jpeg', 'jpg', 'png'],
self::ContentImages => ['jpeg', 'jpg', 'png'],
};
}
public function isImage(): bool
{
return match ($this) {
self::Logo => true,
self::Logo => true,
self::ContentImages => true,
default => false
};
}
public function isVideo(): bool
{
return match ($this->name) {
return match ($this) {
default => false
};
}
public function isAudio(): bool
{
return match ($this->name) {
return match ($this) {
default => false
};
}

View File

@@ -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;
}
}

View File

@@ -4,10 +4,19 @@ namespace App\Http\Requests\Admin\Projects\About;
use App\Contracts\FormRequestDto;
use App\Dto\Service\Admin\Project\About\StoreUpdate;
use App\Dto\Service\Storage\Storages;
use App\Enums\StorageType;
use Illuminate\Foundation\Http\FormRequest;
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.
*/
@@ -16,6 +25,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
return [
'title' => ['required', 'string', 'max:255',],
'description' => ['nullable', 'string',],
'storage.content_images.*.file' => ['numeric', 'min:1'],
];
}
@@ -24,6 +34,18 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
return new StoreUpdate(
title: $this->input('title'),
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;
}
}

View File

@@ -6,10 +6,19 @@ use App\Contracts\FormRequestDto;
use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
use App\Dto\Service\Storage\Storages;
use App\Enums\StorageType;
use Illuminate\Foundation\Http\FormRequest;
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.
*/
@@ -22,6 +31,7 @@ final class StoreUpdateRequest extends FormRequest implements FormRequestDto
'category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'],
'content.*.title' => ['required', 'string', 'max:255'],
'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,
title: $content['title'],
content: $content['content'] ?? '',
storages: $this->contentStorages($content),
));
}
return $contents;
}
private function contentStorages(array $content): Storages
{
$storages = new Storages();
$images = $content['content_images'] ?? [];
$storages->addMany($images, StorageType::ContentImages);
return $storages;
}
}

View File

@@ -6,7 +6,9 @@ use App\Contracts\FormRequestDto;
use App\Dto\Service\Storage\Upload;
use App\Enums\Morph;
use App\Enums\StorageType;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Validation\Rules\Enum;
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')),
);
}
/**
* 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));
}
}

View File

@@ -2,13 +2,15 @@
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\Model;
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';

View File

@@ -2,13 +2,15 @@
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\Model;
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';

View 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;
}
}

View File

@@ -26,4 +26,13 @@ final readonly class ProjectContentPolicy extends Policy
{
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;
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Services\Admin\Project;
use App\Contracts\ServiceResultError;
use App\Dto\Service\Admin\Project\About\StoreUpdate;
use App\Enums\Morph;
use App\Models\ProjectContent;
use App\Models\User;
use App\Repositories\ProjectContentRepository;
@@ -13,6 +14,7 @@ use App\ServiceResults\ServiceResultArray;
use App\ServiceResults\StoreUpdateResult;
use App\Services\ProjectContent\ProjectContentCommandHandler;
use App\Services\Service;
use App\Services\Storage\StorageService;
use Illuminate\Support\Facades\DB;
final class AboutService extends Service
@@ -22,6 +24,7 @@ final class AboutService extends Service
private readonly ProjectLanguageRepository $projectLanguageRepository,
private readonly ProjectContentRepository $projectContentRepository,
private readonly ProjectContentCommandHandler $projectContentCommandHandler,
private readonly StorageService $storageService,
) { }
public function languages(int $projectId, User $user): ServiceResultError | ServiceResultArray
@@ -88,11 +91,19 @@ final class AboutService extends Service
if ($user->cannot('create', ProjectContent::class)) {
return $this->errFobidden(__('Access is denied'));
}
$storages = $this->storageService->getStoragesAndValidate($data->getStorages(), Morph::ProjectContent);
if (!$storages->isSuccess()) {
return $storages;
}
try {
$aboutProject = DB::transaction(function () use ($data, $projectId, $languageId) {
$aboutProject = DB::transaction(function () use ($data, $projectId, $languageId, $storages) {
$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) {
report($e);
@@ -108,11 +119,19 @@ final class AboutService extends Service
return $this->errFobidden(__('Access is denied'));
}
$storages = $this->storageService->getStoragesAndValidate($data->getStorages(), Morph::ProjectContent);
if (!$storages->isSuccess()) {
return $storages;
}
try {
$aboutProject = DB::transaction(function () use ($data, $content) {
$aboutProject = DB::transaction(function () use ($data, $content, $storages) {
$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) {
report($e);

View File

@@ -4,18 +4,33 @@ namespace App\Services\DocumentationContent;
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
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\StorageCommandException;
use App\Models\Documentation;
use App\Models\DocumentationContent;
use App\Models\Project;
final readonly class ModelSyncCommand
{
public function __construct(
private StorageCommand $storageCommand,
) { }
/**
* @throws StorageCommandException
* @throws ContentSaveException
*/
public function execute(Project $project, Documentation $documentation, Contents $contents): void
{
$storageDto = new StorageDto();
$languages = $project->languages;
$documentationContents = $documentation->contents;
$newContents = [];
$contentsStorageCreated = [];
$contentLanguages = [];
foreach ($contents->getContents() as $content) {
/** @var Content $content */
$language = $languages->firstWhere('id', $content->getLanguageId());
@@ -26,15 +41,29 @@ final readonly class ModelSyncCommand
$model = $documentationContents->firstWhere('language_id', $language->id);
$data = $this->getData($content);
if (\is_null($model)) {
$contentsStorageCreated[$content->getLanguageId()] = $content->getStorages();
$newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data);
$contentLanguages[] = $content->getLanguageId();
continue;
}
$storageDto->add($model, $content->getStorages());
$model->update($data);
}
if (!empty($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

View File

@@ -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);
}
}
}

View File

@@ -2,15 +2,17 @@
namespace App\View\Components\Volt\Forms;
use App\Dto\View\Volt\Form\WysiwygStorageUpload;
use Illuminate\Support\Str;
use Illuminate\View\View;
final class TextareaWysiwyg extends Form
{
public function __construct(
private readonly string $title,
private readonly string $name,
private readonly ?string $value = '',
private readonly string $title,
private readonly string $name,
private readonly ?string $value = '',
private readonly ?WysiwygStorageUpload $storageUpload = null,
) { }
protected function getName(): string
@@ -28,6 +30,11 @@ final class TextareaWysiwyg extends Form
return (string) old($this->getRequestName(), $this->value);
}
public function getStorageUpload(): ?WysiwygStorageUpload
{
return $this->storageUpload;
}
/**
* @inheritDoc
*/
@@ -37,10 +44,11 @@ final class TextareaWysiwyg extends Form
return view('components.volt.forms.textarea-wysiwyg', [
'tinymceLicenseKey' => $tinymceLicenseKey,
'title' => $this->getTitle(),
'name' => $this->getName(),
'requestName' => $this->getRequestName(),
'value' => $this->getValue(),
'title' => $this->getTitle(),
'name' => $this->getName(),
'requestName' => $this->getRequestName(),
'value' => $this->getValue(),
'storageUpload' => $this->getStorageUpload(),
]);
}
}