route element file uploads
Add short and long PDF paths to elements, keep pdfPath as a short PDF compatibility alias, and route generic file uploads by fileType.
This commit is contained in:
parent
0d5783ba9c
commit
9bbabc7fa6
28 changed files with 686 additions and 214 deletions
|
|
@ -57,8 +57,7 @@ class ElementController
|
|||
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
$iconImage = $this->uploadedFileInput($request, 'iconImage');
|
||||
$pdf = $this->uploadedFileInput($request, 'pdf');
|
||||
$file = $this->uploadedFileInput($request, 'file');
|
||||
|
||||
try {
|
||||
$element = $this->updateElement->execute(
|
||||
|
|
@ -71,16 +70,15 @@ class ElementController
|
|||
'iconImageUrl',
|
||||
),
|
||||
richText: $this->stringInput($request, 'richText'),
|
||||
shortPdfPath: $this->stringInput($request, 'shortPdfPath'),
|
||||
longPdfPath: $this->stringInput($request, 'longPdfPath'),
|
||||
pdfPath: $this->stringInput($request, 'pdfPath'),
|
||||
youtubeUrl: $this->stringInput($request, 'youtubeUrl'),
|
||||
iconImageContents: $iconImage['contents'],
|
||||
iconImageOriginalName: $iconImage['originalName'],
|
||||
iconImageMimeType: $iconImage['mimeType'],
|
||||
iconImageSizeBytes: $iconImage['sizeBytes'],
|
||||
pdfContents: $pdf['contents'],
|
||||
pdfOriginalName: $pdf['originalName'],
|
||||
pdfMimeType: $pdf['mimeType'],
|
||||
pdfSizeBytes: $pdf['sizeBytes'],
|
||||
fileType: $this->stringInput($request, 'fileType'),
|
||||
fileContents: $file['contents'],
|
||||
fileOriginalName: $file['originalName'],
|
||||
fileMimeType: $file['mimeType'],
|
||||
fileSizeBytes: $file['sizeBytes'],
|
||||
)
|
||||
);
|
||||
} catch (BadRequestException $exception) {
|
||||
|
|
@ -176,6 +174,8 @@ class ElementController
|
|||
* description: string,
|
||||
* iconImageUrl: string|null,
|
||||
* richText: string,
|
||||
* shortPdfPath: string|null,
|
||||
* longPdfPath: string|null,
|
||||
* pdfPath: string|null,
|
||||
* youtubeUrl: string|null,
|
||||
* }
|
||||
|
|
@ -191,8 +191,16 @@ class ElementController
|
|||
'element-icons/',
|
||||
),
|
||||
'richText' => $element->getRichText(),
|
||||
'shortPdfPath' => $this->storageUrl(
|
||||
$element->getShortPdfPath(),
|
||||
'element-pdfs/',
|
||||
),
|
||||
'longPdfPath' => $this->storageUrl(
|
||||
$element->getLongPdfPath(),
|
||||
'element-pdfs/',
|
||||
),
|
||||
'pdfPath' => $this->storageUrl(
|
||||
$element->getPdfPath(),
|
||||
$element->getShortPdfPath(),
|
||||
'element-pdfs/',
|
||||
),
|
||||
'youtubeUrl' => $element->getYoutubeUrl(),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ class CreateElementDto
|
|||
public string $description,
|
||||
public ?string $iconImageUrl,
|
||||
public string $richText,
|
||||
public ?string $pdfPath,
|
||||
public ?string $shortPdfPath,
|
||||
public ?string $longPdfPath,
|
||||
public ?string $youtubeUrl,
|
||||
public ?Element $parentElement,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ class Element
|
|||
private string $description,
|
||||
private ?string $iconImageUrl,
|
||||
private string $richText,
|
||||
private ?string $pdfPath,
|
||||
private ?string $shortPdfPath,
|
||||
private ?string $longPdfPath,
|
||||
private ?string $youtubeUrl,
|
||||
private Set $set,
|
||||
private ?Element $parentElement,
|
||||
|
|
@ -64,14 +65,24 @@ class Element
|
|||
$this->richText = $richText;
|
||||
}
|
||||
|
||||
public function getPdfPath(): ?string
|
||||
public function getShortPdfPath(): ?string
|
||||
{
|
||||
return $this->pdfPath;
|
||||
return $this->shortPdfPath;
|
||||
}
|
||||
|
||||
public function setPdfPath(?string $pdfPath): void
|
||||
public function setShortPdfPath(?string $shortPdfPath): void
|
||||
{
|
||||
$this->pdfPath = $pdfPath;
|
||||
$this->shortPdfPath = $shortPdfPath;
|
||||
}
|
||||
|
||||
public function getLongPdfPath(): ?string
|
||||
{
|
||||
return $this->longPdfPath;
|
||||
}
|
||||
|
||||
public function setLongPdfPath(?string $longPdfPath): void
|
||||
{
|
||||
$this->longPdfPath = $longPdfPath;
|
||||
}
|
||||
|
||||
public function getYoutubeUrl(): ?string
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property string $description
|
||||
* @property string|null $icon_image_url
|
||||
* @property string $rich_text
|
||||
* @property string|null $pdf_path
|
||||
* @property string|null $short_pdf_path
|
||||
* @property string|null $long_pdf_path
|
||||
* @property string|null $youtube_url
|
||||
* @property int|null $parent_element_id
|
||||
*
|
||||
|
|
@ -26,7 +27,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @method static Builder<static>|ElementModel whereDescription($value)
|
||||
* @method static Builder<static>|ElementModel whereIconImageUrl($value)
|
||||
* @method static Builder<static>|ElementModel whereRichText($value)
|
||||
* @method static Builder<static>|ElementModel wherePdfPath($value)
|
||||
* @method static Builder<static>|ElementModel whereShortPdfPath($value)
|
||||
* @method static Builder<static>|ElementModel whereLongPdfPath($value)
|
||||
* @method static Builder<static>|ElementModel whereYoutubeUrl($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
|
|
@ -43,7 +45,8 @@ class ElementModel extends Model
|
|||
'description',
|
||||
'icon_image_url',
|
||||
'rich_text',
|
||||
'pdf_path',
|
||||
'short_pdf_path',
|
||||
'long_pdf_path',
|
||||
'youtube_url',
|
||||
'parent_element_id',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ class EloquentElementRepository implements ElementRepository
|
|||
'description' => $dto->description,
|
||||
'icon_image_url' => $dto->iconImageUrl,
|
||||
'rich_text' => $dto->richText,
|
||||
'pdf_path' => $dto->pdfPath,
|
||||
'short_pdf_path' => $dto->shortPdfPath,
|
||||
'long_pdf_path' => $dto->longPdfPath,
|
||||
'youtube_url' => $dto->youtubeUrl,
|
||||
'parent_element_id' => $dto->parentElement?->getId(),
|
||||
]);
|
||||
|
|
@ -31,7 +32,8 @@ class EloquentElementRepository implements ElementRepository
|
|||
description: $dto->description,
|
||||
iconImageUrl: $dto->iconImageUrl,
|
||||
richText: $dto->richText,
|
||||
pdfPath: $dto->pdfPath,
|
||||
shortPdfPath: $dto->shortPdfPath,
|
||||
longPdfPath: $dto->longPdfPath,
|
||||
youtubeUrl: $dto->youtubeUrl,
|
||||
set: $dto->set,
|
||||
parentElement: $dto->parentElement,
|
||||
|
|
@ -52,7 +54,8 @@ class EloquentElementRepository implements ElementRepository
|
|||
$model->description = $element->getDescription();
|
||||
$model->icon_image_url = $element->getIconImageUrl();
|
||||
$model->rich_text = $element->getRichText();
|
||||
$model->pdf_path = $element->getPdfPath();
|
||||
$model->short_pdf_path = $element->getShortPdfPath();
|
||||
$model->long_pdf_path = $element->getLongPdfPath();
|
||||
$model->youtube_url = $element->getYoutubeUrl();
|
||||
$model->parent_element_id = $element->getParentElement()?->getId();
|
||||
$model->save();
|
||||
|
|
@ -137,7 +140,8 @@ class EloquentElementRepository implements ElementRepository
|
|||
description: $model->description,
|
||||
iconImageUrl: $model->icon_image_url,
|
||||
richText: $model->rich_text,
|
||||
pdfPath: $model->pdf_path,
|
||||
shortPdfPath: $model->short_pdf_path,
|
||||
longPdfPath: $model->long_pdf_path,
|
||||
youtubeUrl: $model->youtube_url,
|
||||
set: $set,
|
||||
parentElement: $parentElement,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,12 @@ class CreateElement
|
|||
? null
|
||||
: $request->iconImageUrl;
|
||||
$richText = $request->richText ?? '';
|
||||
$pdfPath = $request->pdfPath === '' ? null : $request->pdfPath;
|
||||
$shortPdfPath = $request->shortPdfPath === ''
|
||||
? null
|
||||
: $request->shortPdfPath;
|
||||
$longPdfPath = $request->longPdfPath === ''
|
||||
? null
|
||||
: $request->longPdfPath;
|
||||
$youtubeUrl = $request->youtubeUrl === ''
|
||||
? null
|
||||
: $request->youtubeUrl;
|
||||
|
|
@ -56,7 +61,8 @@ class CreateElement
|
|||
description: $description,
|
||||
iconImageUrl: $iconImageUrl,
|
||||
richText: $richText,
|
||||
pdfPath: $pdfPath,
|
||||
shortPdfPath: $shortPdfPath,
|
||||
longPdfPath: $longPdfPath,
|
||||
youtubeUrl: $youtubeUrl,
|
||||
parentElement: null,
|
||||
));
|
||||
|
|
@ -82,7 +88,8 @@ class CreateElement
|
|||
description: $description,
|
||||
iconImageUrl: $iconImageUrl,
|
||||
richText: $richText,
|
||||
pdfPath: $pdfPath,
|
||||
shortPdfPath: $shortPdfPath,
|
||||
longPdfPath: $longPdfPath,
|
||||
youtubeUrl: $youtubeUrl,
|
||||
parentElement: $parentElement,
|
||||
));
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ class CreateElementRequest
|
|||
public ?string $description,
|
||||
public ?string $iconImageUrl,
|
||||
public ?string $richText,
|
||||
public ?string $pdfPath,
|
||||
public ?string $shortPdfPath,
|
||||
public ?string $longPdfPath,
|
||||
public ?string $youtubeUrl,
|
||||
public ?int $parentElementId,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -9,15 +9,23 @@ use App\Exceptions\NotFoundException;
|
|||
|
||||
class UpdateElement
|
||||
{
|
||||
private const FILE_TYPE_ICON_IMAGE = 'iconImage';
|
||||
|
||||
private const FILE_TYPE_SHORT_PDF = 'shortPdf';
|
||||
|
||||
private const FILE_TYPE_LONG_PDF = 'longPdf';
|
||||
|
||||
public function __construct(
|
||||
private UpdateTitle $updateTitle,
|
||||
private UpdateDescription $updateDescription,
|
||||
private UpdateIconImageUrl $updateIconImageUrl,
|
||||
private UpdateRichText $updateRichText,
|
||||
private UpdatePdfPath $updatePdfPath,
|
||||
private UpdateShortPdfPath $updateShortPdfPath,
|
||||
private UpdateLongPdfPath $updateLongPdfPath,
|
||||
private UpdateYoutubeUrl $updateYoutubeUrl,
|
||||
private UpdateIconImage $updateIconImage,
|
||||
private UpdatePdf $updatePdf,
|
||||
private UpdateShortPdf $updateShortPdf,
|
||||
private UpdateLongPdf $updateLongPdf,
|
||||
private ElementRepository $elementRepository,
|
||||
) {
|
||||
}
|
||||
|
|
@ -36,10 +44,15 @@ class UpdateElement
|
|||
&& $request->description === null
|
||||
&& $request->iconImageUrl === null
|
||||
&& $request->richText === null
|
||||
&& $request->shortPdfPath === null
|
||||
&& $request->longPdfPath === null
|
||||
&& $request->pdfPath === null
|
||||
&& $request->youtubeUrl === null
|
||||
&& $request->iconImageContents === null
|
||||
&& $request->pdfContents === null;
|
||||
&& $request->fileType === null
|
||||
&& $request->fileContents === null
|
||||
&& $request->fileOriginalName === null
|
||||
&& $request->fileMimeType === null
|
||||
&& $request->fileSizeBytes === null;
|
||||
if ($hasNoFields) {
|
||||
throw new BadRequestException('nothing to update');
|
||||
}
|
||||
|
|
@ -70,10 +83,21 @@ class UpdateElement
|
|||
richText: $request->richText,
|
||||
));
|
||||
}
|
||||
if ($request->pdfPath !== null) {
|
||||
$this->updatePdfPath->execute(new UpdatePdfPathRequest(
|
||||
if ($request->shortPdfPath !== null) {
|
||||
$this->updateShortPdfPath->execute(new UpdateShortPdfPathRequest(
|
||||
id: $request->id,
|
||||
pdfPath: $request->pdfPath,
|
||||
shortPdfPath: $request->shortPdfPath,
|
||||
));
|
||||
} elseif ($request->pdfPath !== null) {
|
||||
$this->updateShortPdfPath->execute(new UpdateShortPdfPathRequest(
|
||||
id: $request->id,
|
||||
shortPdfPath: $request->pdfPath,
|
||||
));
|
||||
}
|
||||
if ($request->longPdfPath !== null) {
|
||||
$this->updateLongPdfPath->execute(new UpdateLongPdfPathRequest(
|
||||
id: $request->id,
|
||||
longPdfPath: $request->longPdfPath,
|
||||
));
|
||||
}
|
||||
if ($request->youtubeUrl !== null) {
|
||||
|
|
@ -82,27 +106,8 @@ class UpdateElement
|
|||
youtubeUrl: $request->youtubeUrl,
|
||||
));
|
||||
}
|
||||
if ($request->iconImageContents !== null) {
|
||||
$this->updateIconImage->execute(
|
||||
new UpdateIconImageRequest(
|
||||
id: $request->id,
|
||||
iconImageContents: $request->iconImageContents,
|
||||
iconImageOriginalName: $request->iconImageOriginalName,
|
||||
iconImageMimeType: $request->iconImageMimeType,
|
||||
iconImageSizeBytes: $request->iconImageSizeBytes,
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($request->pdfContents !== null) {
|
||||
$this->updatePdf->execute(
|
||||
new UpdatePdfRequest(
|
||||
id: $request->id,
|
||||
pdfContents: $request->pdfContents,
|
||||
pdfOriginalName: $request->pdfOriginalName,
|
||||
pdfMimeType: $request->pdfMimeType,
|
||||
pdfSizeBytes: $request->pdfSizeBytes,
|
||||
)
|
||||
);
|
||||
if ($this->hasFileInput($request)) {
|
||||
$this->updateFile($request);
|
||||
}
|
||||
|
||||
$element = $this->elementRepository->find($request->id);
|
||||
|
|
@ -112,4 +117,68 @@ class UpdateElement
|
|||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequestException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function updateFile(UpdateElementRequest $request): void
|
||||
{
|
||||
if ($request->fileType === null) {
|
||||
throw new BadRequestException('fileType is required');
|
||||
}
|
||||
|
||||
if ($request->fileType === self::FILE_TYPE_ICON_IMAGE) {
|
||||
$this->updateIconImage->execute(
|
||||
new UpdateIconImageRequest(
|
||||
id: $request->id,
|
||||
fileContents: $request->fileContents,
|
||||
fileOriginalName: $request->fileOriginalName,
|
||||
fileMimeType: $request->fileMimeType,
|
||||
fileSizeBytes: $request->fileSizeBytes,
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($request->fileType === self::FILE_TYPE_SHORT_PDF) {
|
||||
$this->updateShortPdf->execute(
|
||||
new UpdateShortPdfRequest(
|
||||
id: $request->id,
|
||||
fileContents: $request->fileContents,
|
||||
fileOriginalName: $request->fileOriginalName,
|
||||
fileMimeType: $request->fileMimeType,
|
||||
fileSizeBytes: $request->fileSizeBytes,
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($request->fileType === self::FILE_TYPE_LONG_PDF) {
|
||||
$this->updateLongPdf->execute(
|
||||
new UpdateLongPdfRequest(
|
||||
id: $request->id,
|
||||
fileContents: $request->fileContents,
|
||||
fileOriginalName: $request->fileOriginalName,
|
||||
fileMimeType: $request->fileMimeType,
|
||||
fileSizeBytes: $request->fileSizeBytes,
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BadRequestException('fileType is invalid');
|
||||
}
|
||||
|
||||
private function hasFileInput(UpdateElementRequest $request): bool
|
||||
{
|
||||
return $request->fileType !== null
|
||||
|| $request->fileContents !== null
|
||||
|| $request->fileOriginalName !== null
|
||||
|| $request->fileMimeType !== null
|
||||
|| $request->fileSizeBytes !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,16 +10,15 @@ class UpdateElementRequest
|
|||
public ?string $description,
|
||||
public ?string $iconImageUrl,
|
||||
public ?string $richText,
|
||||
public ?string $shortPdfPath,
|
||||
public ?string $longPdfPath,
|
||||
public ?string $pdfPath,
|
||||
public ?string $youtubeUrl,
|
||||
public ?string $iconImageContents,
|
||||
public ?string $iconImageOriginalName,
|
||||
public ?string $iconImageMimeType,
|
||||
public ?int $iconImageSizeBytes,
|
||||
public ?string $pdfContents,
|
||||
public ?string $pdfOriginalName,
|
||||
public ?string $pdfMimeType,
|
||||
public ?int $pdfSizeBytes,
|
||||
public ?string $fileType,
|
||||
public ?string $fileContents,
|
||||
public ?string $fileOriginalName,
|
||||
public ?string $fileMimeType,
|
||||
public ?int $fileSizeBytes,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,17 +36,17 @@ class UpdateIconImage
|
|||
}
|
||||
|
||||
if (
|
||||
$request->iconImageContents === null
|
||||
|| $request->iconImageOriginalName === null
|
||||
|| $request->iconImageMimeType === null
|
||||
|| $request->iconImageSizeBytes === null
|
||||
$request->fileContents === null
|
||||
|| $request->fileOriginalName === null
|
||||
|| $request->fileMimeType === null
|
||||
|| $request->fileSizeBytes === null
|
||||
) {
|
||||
throw new BadRequestException('icon image is required');
|
||||
}
|
||||
|
||||
if (
|
||||
! in_array(
|
||||
$request->iconImageMimeType,
|
||||
$request->fileMimeType,
|
||||
self::ALLOWED_MIME_TYPES,
|
||||
true,
|
||||
)
|
||||
|
|
@ -56,7 +56,7 @@ class UpdateIconImage
|
|||
);
|
||||
}
|
||||
|
||||
if ($request->iconImageSizeBytes > self::MAX_SIZE_BYTES) {
|
||||
if ($request->fileSizeBytes > self::MAX_SIZE_BYTES) {
|
||||
throw new BadRequestException(
|
||||
'icon image must be 5MB or smaller',
|
||||
);
|
||||
|
|
@ -68,10 +68,10 @@ class UpdateIconImage
|
|||
}
|
||||
|
||||
$iconImage = new FileToUpload(
|
||||
contents: $request->iconImageContents,
|
||||
originalName: $request->iconImageOriginalName,
|
||||
mimeType: $request->iconImageMimeType,
|
||||
sizeBytes: $request->iconImageSizeBytes,
|
||||
contents: $request->fileContents,
|
||||
originalName: $request->fileOriginalName,
|
||||
mimeType: $request->fileMimeType,
|
||||
sizeBytes: $request->fileSizeBytes,
|
||||
);
|
||||
$path = $this->fileUploader->upload($iconImage, 'element-icons');
|
||||
$element->setIconImageUrl($path);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ class UpdateIconImageRequest
|
|||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public ?string $iconImageContents,
|
||||
public ?string $iconImageOriginalName,
|
||||
public ?string $iconImageMimeType,
|
||||
public ?int $iconImageSizeBytes,
|
||||
public ?string $fileContents,
|
||||
public ?string $fileOriginalName,
|
||||
public ?string $fileMimeType,
|
||||
public ?int $fileSizeBytes,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use App\Exceptions\NotFoundException;
|
|||
use App\Shared\Files\FileToUpload;
|
||||
use App\Shared\Files\FileUploader;
|
||||
|
||||
class UpdatePdf
|
||||
class UpdateLongPdf
|
||||
{
|
||||
private const ALLOWED_MIME_TYPES = [
|
||||
'application/pdf',
|
||||
|
|
@ -27,24 +27,24 @@ class UpdatePdf
|
|||
* @throws BadRequestException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function execute(UpdatePdfRequest $request): Element
|
||||
public function execute(UpdateLongPdfRequest $request): Element
|
||||
{
|
||||
if ($request->id === null) {
|
||||
throw new BadRequestException('id is required');
|
||||
}
|
||||
|
||||
if (
|
||||
$request->pdfContents === null
|
||||
|| $request->pdfOriginalName === null
|
||||
|| $request->pdfMimeType === null
|
||||
|| $request->pdfSizeBytes === null
|
||||
$request->fileContents === null
|
||||
|| $request->fileOriginalName === null
|
||||
|| $request->fileMimeType === null
|
||||
|| $request->fileSizeBytes === null
|
||||
) {
|
||||
throw new BadRequestException('pdf is required');
|
||||
}
|
||||
|
||||
if (
|
||||
! in_array(
|
||||
$request->pdfMimeType,
|
||||
$request->fileMimeType,
|
||||
self::ALLOWED_MIME_TYPES,
|
||||
true,
|
||||
)
|
||||
|
|
@ -52,7 +52,7 @@ class UpdatePdf
|
|||
throw new BadRequestException('pdf must be a pdf file');
|
||||
}
|
||||
|
||||
if ($request->pdfSizeBytes > self::MAX_SIZE_BYTES) {
|
||||
if ($request->fileSizeBytes > self::MAX_SIZE_BYTES) {
|
||||
throw new BadRequestException('pdf must be 10MB or smaller');
|
||||
}
|
||||
|
||||
|
|
@ -62,13 +62,13 @@ class UpdatePdf
|
|||
}
|
||||
|
||||
$pdf = new FileToUpload(
|
||||
contents: $request->pdfContents,
|
||||
originalName: $request->pdfOriginalName,
|
||||
mimeType: $request->pdfMimeType,
|
||||
sizeBytes: $request->pdfSizeBytes,
|
||||
contents: $request->fileContents,
|
||||
originalName: $request->fileOriginalName,
|
||||
mimeType: $request->fileMimeType,
|
||||
sizeBytes: $request->fileSizeBytes,
|
||||
);
|
||||
$path = $this->fileUploader->upload($pdf, 'element-pdfs');
|
||||
$element->setPdfPath($path);
|
||||
$path = $this->fileUploader->upload($pdf, 'element-pdfs/long');
|
||||
$element->setLongPdfPath($path);
|
||||
|
||||
return $this->elementRepository->update($element);
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ use App\Exceptions\BadRequestException;
|
|||
use App\Exceptions\NotFoundException;
|
||||
use App\Shared\Files\FileUploader;
|
||||
|
||||
class UpdatePdfPath
|
||||
class UpdateLongPdfPath
|
||||
{
|
||||
public function __construct(
|
||||
private ElementRepository $elementRepository,
|
||||
|
|
@ -20,14 +20,14 @@ class UpdatePdfPath
|
|||
* @throws BadRequestException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function execute(UpdatePdfPathRequest $request): Element
|
||||
public function execute(UpdateLongPdfPathRequest $request): Element
|
||||
{
|
||||
if ($request->id === null) {
|
||||
throw new BadRequestException('id is required');
|
||||
}
|
||||
|
||||
if ($request->pdfPath === null) {
|
||||
throw new BadRequestException('pdfPath is required');
|
||||
if ($request->longPdfPath === null) {
|
||||
throw new BadRequestException('longPdfPath is required');
|
||||
}
|
||||
|
||||
$element = $this->elementRepository->find($request->id);
|
||||
|
|
@ -35,12 +35,12 @@ class UpdatePdfPath
|
|||
throw new NotFoundException('Element not found');
|
||||
}
|
||||
|
||||
$pdfPath = $this->nullableString($request->pdfPath);
|
||||
if ($pdfPath === null) {
|
||||
$this->deleteManagedFile($element->getPdfPath());
|
||||
$longPdfPath = $this->nullableString($request->longPdfPath);
|
||||
if ($longPdfPath === null) {
|
||||
$this->deleteManagedFile($element->getLongPdfPath());
|
||||
}
|
||||
|
||||
$element->setPdfPath($pdfPath);
|
||||
$element->setLongPdfPath($longPdfPath);
|
||||
|
||||
return $this->elementRepository->update($element);
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
class UpdatePdfPathRequest
|
||||
class UpdateLongPdfPathRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public ?string $pdfPath,
|
||||
public ?string $longPdfPath,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
class UpdateLongPdfRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public ?string $fileContents,
|
||||
public ?string $fileOriginalName,
|
||||
public ?string $fileMimeType,
|
||||
public ?int $fileSizeBytes,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
class UpdatePdfRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public ?string $pdfContents,
|
||||
public ?string $pdfOriginalName,
|
||||
public ?string $pdfMimeType,
|
||||
public ?int $pdfSizeBytes,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
use App\Element\Element;
|
||||
use App\Element\ElementRepository;
|
||||
use App\Exceptions\BadRequestException;
|
||||
use App\Exceptions\NotFoundException;
|
||||
use App\Shared\Files\FileToUpload;
|
||||
use App\Shared\Files\FileUploader;
|
||||
|
||||
class UpdateShortPdf
|
||||
{
|
||||
private const ALLOWED_MIME_TYPES = [
|
||||
'application/pdf',
|
||||
];
|
||||
|
||||
private const MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
public function __construct(
|
||||
private ElementRepository $elementRepository,
|
||||
private FileUploader $fileUploader,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequestException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function execute(UpdateShortPdfRequest $request): Element
|
||||
{
|
||||
if ($request->id === null) {
|
||||
throw new BadRequestException('id is required');
|
||||
}
|
||||
|
||||
if (
|
||||
$request->fileContents === null
|
||||
|| $request->fileOriginalName === null
|
||||
|| $request->fileMimeType === null
|
||||
|| $request->fileSizeBytes === null
|
||||
) {
|
||||
throw new BadRequestException('pdf is required');
|
||||
}
|
||||
|
||||
if (
|
||||
! in_array(
|
||||
$request->fileMimeType,
|
||||
self::ALLOWED_MIME_TYPES,
|
||||
true,
|
||||
)
|
||||
) {
|
||||
throw new BadRequestException('pdf must be a pdf file');
|
||||
}
|
||||
|
||||
if ($request->fileSizeBytes > self::MAX_SIZE_BYTES) {
|
||||
throw new BadRequestException('pdf must be 10MB or smaller');
|
||||
}
|
||||
|
||||
$element = $this->elementRepository->find($request->id);
|
||||
if ($element === null) {
|
||||
throw new NotFoundException('Element not found');
|
||||
}
|
||||
|
||||
$pdf = new FileToUpload(
|
||||
contents: $request->fileContents,
|
||||
originalName: $request->fileOriginalName,
|
||||
mimeType: $request->fileMimeType,
|
||||
sizeBytes: $request->fileSizeBytes,
|
||||
);
|
||||
$path = $this->fileUploader->upload($pdf, 'element-pdfs/short');
|
||||
$element->setShortPdfPath($path);
|
||||
|
||||
return $this->elementRepository->update($element);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
use App\Element\Element;
|
||||
use App\Element\ElementRepository;
|
||||
use App\Exceptions\BadRequestException;
|
||||
use App\Exceptions\NotFoundException;
|
||||
use App\Shared\Files\FileUploader;
|
||||
|
||||
class UpdateShortPdfPath
|
||||
{
|
||||
public function __construct(
|
||||
private ElementRepository $elementRepository,
|
||||
private FileUploader $fileUploader,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequestException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function execute(UpdateShortPdfPathRequest $request): Element
|
||||
{
|
||||
if ($request->id === null) {
|
||||
throw new BadRequestException('id is required');
|
||||
}
|
||||
|
||||
if ($request->shortPdfPath === null) {
|
||||
throw new BadRequestException('shortPdfPath is required');
|
||||
}
|
||||
|
||||
$element = $this->elementRepository->find($request->id);
|
||||
if ($element === null) {
|
||||
throw new NotFoundException('Element not found');
|
||||
}
|
||||
|
||||
$shortPdfPath = $this->nullableString($request->shortPdfPath);
|
||||
if ($shortPdfPath === null) {
|
||||
$this->deleteManagedFile($element->getShortPdfPath());
|
||||
}
|
||||
|
||||
$element->setShortPdfPath($shortPdfPath);
|
||||
|
||||
return $this->elementRepository->update($element);
|
||||
}
|
||||
|
||||
private function nullableString(string $value): ?string
|
||||
{
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function deleteManagedFile(?string $path): void
|
||||
{
|
||||
if ($path === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fileUploader->delete($path);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
class UpdateShortPdfPathRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public ?string $shortPdfPath,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Element\UseCases\UpdateElement;
|
||||
|
||||
class UpdateShortPdfRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id,
|
||||
public ?string $fileContents,
|
||||
public ?string $fileOriginalName,
|
||||
public ?string $fileMimeType,
|
||||
public ?int $fileSizeBytes,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ return new class extends Migration
|
|||
$table->text('description')->default('');
|
||||
$table->string('icon_image_url')->nullable();
|
||||
$table->text('rich_text')->default('');
|
||||
$table->string('pdf_path')->nullable();
|
||||
$table->string('short_pdf_path')->nullable();
|
||||
$table->string('long_pdf_path')->nullable();
|
||||
$table->string('youtube_url')->nullable();
|
||||
$table->foreignId('parent_element_id')
|
||||
->nullable()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class ElementSeeder extends Seeder
|
|||
'element-icons/baderech-haavodah-icon.png',
|
||||
);
|
||||
$introductionPdfPath = $this->seedStorageFile(
|
||||
'element-pdfs/baderech.pdf',
|
||||
'element-pdfs/short/baderech.pdf',
|
||||
);
|
||||
|
||||
$rootElement = $elementRepository->create(new CreateElementDto(
|
||||
|
|
@ -29,7 +29,8 @@ class ElementSeeder extends Seeder
|
|||
description: $baderechSet->getDescription(),
|
||||
iconImageUrl: $rootIconImageUrl,
|
||||
richText: '',
|
||||
pdfPath: null,
|
||||
shortPdfPath: null,
|
||||
longPdfPath: null,
|
||||
youtubeUrl: null,
|
||||
parentElement: null,
|
||||
));
|
||||
|
|
@ -44,7 +45,7 @@ class ElementSeeder extends Seeder
|
|||
. 'unlock the ability to live with strength, confidence, '
|
||||
. 'and hope.',
|
||||
'richText' => $this->introductionRichText(),
|
||||
'pdfPath' => $introductionPdfPath,
|
||||
'shortPdfPath' => $introductionPdfPath,
|
||||
],
|
||||
[
|
||||
'title' => '2. Foundations',
|
||||
|
|
@ -54,7 +55,7 @@ class ElementSeeder extends Seeder
|
|||
. 'ability to uplift the physical world with spiritual '
|
||||
. 'intention.',
|
||||
'richText' => '',
|
||||
'pdfPath' => null,
|
||||
'shortPdfPath' => null,
|
||||
],
|
||||
[
|
||||
'title' => '3. Divine Plan',
|
||||
|
|
@ -62,14 +63,14 @@ class ElementSeeder extends Seeder
|
|||
. 'generation and the call to remain hopeful, '
|
||||
. 'optimistic, and resilient.',
|
||||
'richText' => '',
|
||||
'pdfPath' => null,
|
||||
'shortPdfPath' => null,
|
||||
],
|
||||
[
|
||||
'title' => '4. Architecture of the Soul',
|
||||
'description' => "Becoming a Baal Da'at - A Master of "
|
||||
. 'Inner Awareness',
|
||||
'richText' => '',
|
||||
'pdfPath' => null,
|
||||
'shortPdfPath' => null,
|
||||
],
|
||||
[
|
||||
'title' => '5. Arba Yesodot',
|
||||
|
|
@ -77,7 +78,7 @@ class ElementSeeder extends Seeder
|
|||
. 'Physical & Financial Health, Relationships, Wisdom '
|
||||
. 'and Guidance.',
|
||||
'richText' => '',
|
||||
'pdfPath' => null,
|
||||
'shortPdfPath' => null,
|
||||
],
|
||||
[
|
||||
'title' => '6. Fluid Integration',
|
||||
|
|
@ -85,7 +86,7 @@ class ElementSeeder extends Seeder
|
|||
. 'engender a shift in mindset, embed healthy routines, '
|
||||
. 'positive life practices.',
|
||||
'richText' => '',
|
||||
'pdfPath' => null,
|
||||
'shortPdfPath' => null,
|
||||
],
|
||||
];
|
||||
|
||||
|
|
@ -97,7 +98,8 @@ class ElementSeeder extends Seeder
|
|||
description: $baderechChildElement['description'],
|
||||
iconImageUrl: null,
|
||||
richText: $baderechChildElement['richText'],
|
||||
pdfPath: $baderechChildElement['pdfPath'],
|
||||
shortPdfPath: $baderechChildElement['shortPdfPath'],
|
||||
longPdfPath: null,
|
||||
youtubeUrl: null,
|
||||
parentElement: $rootElement,
|
||||
));
|
||||
|
|
@ -117,7 +119,8 @@ class ElementSeeder extends Seeder
|
|||
description: '',
|
||||
iconImageUrl: null,
|
||||
richText: '',
|
||||
pdfPath: null,
|
||||
shortPdfPath: null,
|
||||
longPdfPath: null,
|
||||
youtubeUrl: 'https://www.youtube.com/watch?v='
|
||||
. 'yHx-r4p6hHU&t=1s',
|
||||
parentElement: $introductionElement,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class FakeElementRepository implements ElementRepository
|
|||
description: $dto->description,
|
||||
iconImageUrl: $dto->iconImageUrl,
|
||||
richText: $dto->richText,
|
||||
pdfPath: $dto->pdfPath,
|
||||
shortPdfPath: $dto->shortPdfPath,
|
||||
longPdfPath: $dto->longPdfPath,
|
||||
youtubeUrl: $dto->youtubeUrl,
|
||||
set: $dto->set,
|
||||
parentElement: $dto->parentElement,
|
||||
|
|
@ -111,7 +112,8 @@ class FakeElementRepository implements ElementRepository
|
|||
description: $element->getDescription(),
|
||||
iconImageUrl: $element->getIconImageUrl(),
|
||||
richText: $element->getRichText(),
|
||||
pdfPath: $element->getPdfPath(),
|
||||
shortPdfPath: $element->getShortPdfPath(),
|
||||
longPdfPath: $element->getLongPdfPath(),
|
||||
youtubeUrl: $element->getYoutubeUrl(),
|
||||
set: $element->getSet(),
|
||||
parentElement: $parentElement,
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ class ElementsEndpointTest extends TestCase
|
|||
]);
|
||||
}
|
||||
|
||||
public function testAuthenticatedIconImageUploadReturnsElementPayload(): void
|
||||
public function testAuthenticatedIconImageUploadReturnsPayload(): void
|
||||
{
|
||||
Storage::fake('public');
|
||||
$setRepository = app(SetRepository::class);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ref } from 'vue'
|
||||
import { ref, type Ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export interface ChildElement {
|
||||
|
|
@ -10,10 +10,14 @@ export interface ChildElement {
|
|||
export interface Element extends ChildElement {
|
||||
iconImageUrl: string | null
|
||||
richText: string
|
||||
shortPdfPath: string | null
|
||||
longPdfPath: string | null
|
||||
pdfPath: string | null
|
||||
youtubeUrl: string | null
|
||||
}
|
||||
|
||||
type ElementFileType = 'iconImage' | 'shortPdf' | 'longPdf'
|
||||
|
||||
interface ElementResponse {
|
||||
element: Element
|
||||
childElements: ChildElement[]
|
||||
|
|
@ -35,6 +39,8 @@ interface ElementPatchInput {
|
|||
description?: string
|
||||
iconImageUrl?: string
|
||||
richText?: string
|
||||
shortPdfPath?: string
|
||||
longPdfPath?: string
|
||||
pdfPath?: string
|
||||
youtubeUrl?: string
|
||||
}
|
||||
|
|
@ -53,7 +59,8 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
const error = ref<string | null>(null)
|
||||
const isSaving = ref(false)
|
||||
const isUploadingIconImage = ref(false)
|
||||
const isUploadingPdf = ref(false)
|
||||
const isUploadingShortPdf = ref(false)
|
||||
const isUploadingLongPdf = ref(false)
|
||||
const saveError = ref<string | null>(null)
|
||||
|
||||
async function fetchElement(elementId: string): Promise<void> {
|
||||
|
|
@ -89,16 +96,35 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateElement(elementId: string, input: UpdateElementInput): Promise<boolean> {
|
||||
async function updateElement(
|
||||
elementId: string,
|
||||
input: UpdateElementInput,
|
||||
): Promise<boolean> {
|
||||
return await saveElementPatch(elementId, input, 'Could not save element')
|
||||
}
|
||||
|
||||
async function clearElementIconImage(elementId: string): Promise<boolean> {
|
||||
return await saveElementPatch(elementId, { iconImageUrl: '' }, 'Could not remove icon image')
|
||||
return await saveElementPatch(
|
||||
elementId,
|
||||
{ iconImageUrl: '' },
|
||||
'Could not remove icon image',
|
||||
)
|
||||
}
|
||||
|
||||
async function clearElementPdf(elementId: string): Promise<boolean> {
|
||||
return await saveElementPatch(elementId, { pdfPath: '' }, 'Could not remove PDF')
|
||||
async function clearElementShortPdf(elementId: string): Promise<boolean> {
|
||||
return await saveElementPatch(
|
||||
elementId,
|
||||
{ shortPdfPath: '' },
|
||||
'Could not remove short PDF',
|
||||
)
|
||||
}
|
||||
|
||||
async function clearElementLongPdf(elementId: string): Promise<boolean> {
|
||||
return await saveElementPatch(
|
||||
elementId,
|
||||
{ longPdfPath: '' },
|
||||
'Could not remove long PDF',
|
||||
)
|
||||
}
|
||||
|
||||
async function saveElementPatch(
|
||||
|
|
@ -126,38 +152,64 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function uploadElementIconImage(elementId: string, file: File): Promise<boolean> {
|
||||
saveError.value = null
|
||||
isUploadingIconImage.value = true
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('elementId', elementId)
|
||||
formData.append('iconImage', file)
|
||||
const response = await fetch(ELEMENT_UPDATE_URL, {
|
||||
method: 'POST',
|
||||
headers: { Accept: 'application/json' },
|
||||
credentials: 'include',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
return await handleElementResponse(response, 'Could not upload icon image')
|
||||
} catch {
|
||||
saveError.value = 'Network error - could not upload icon image'
|
||||
return false
|
||||
} finally {
|
||||
isUploadingIconImage.value = false
|
||||
}
|
||||
async function uploadElementIconImage(
|
||||
elementId: string,
|
||||
file: File,
|
||||
): Promise<boolean> {
|
||||
return await uploadElementFile(
|
||||
elementId,
|
||||
file,
|
||||
'iconImage',
|
||||
isUploadingIconImage,
|
||||
'Could not upload icon image',
|
||||
'Network error - could not upload icon image',
|
||||
)
|
||||
}
|
||||
|
||||
async function uploadElementPdf(elementId: string, file: File): Promise<boolean> {
|
||||
async function uploadElementShortPdf(
|
||||
elementId: string,
|
||||
file: File,
|
||||
): Promise<boolean> {
|
||||
return await uploadElementFile(
|
||||
elementId,
|
||||
file,
|
||||
'shortPdf',
|
||||
isUploadingShortPdf,
|
||||
'Could not upload short PDF',
|
||||
'Network error - could not upload short PDF',
|
||||
)
|
||||
}
|
||||
|
||||
async function uploadElementLongPdf(
|
||||
elementId: string,
|
||||
file: File,
|
||||
): Promise<boolean> {
|
||||
return await uploadElementFile(
|
||||
elementId,
|
||||
file,
|
||||
'longPdf',
|
||||
isUploadingLongPdf,
|
||||
'Could not upload long PDF',
|
||||
'Network error - could not upload long PDF',
|
||||
)
|
||||
}
|
||||
|
||||
async function uploadElementFile(
|
||||
elementId: string,
|
||||
file: File,
|
||||
fileType: ElementFileType,
|
||||
isUploadingFile: Ref<boolean>,
|
||||
failureMessage: string,
|
||||
networkErrorMessage: string,
|
||||
): Promise<boolean> {
|
||||
saveError.value = null
|
||||
isUploadingPdf.value = true
|
||||
isUploadingFile.value = true
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('elementId', elementId)
|
||||
formData.append('pdf', file)
|
||||
formData.append('fileType', fileType)
|
||||
formData.append('file', file)
|
||||
const response = await fetch(ELEMENT_UPDATE_URL, {
|
||||
method: 'POST',
|
||||
headers: { Accept: 'application/json' },
|
||||
|
|
@ -165,12 +217,12 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
body: formData,
|
||||
})
|
||||
|
||||
return await handleElementResponse(response, 'Could not upload PDF')
|
||||
return await handleElementResponse(response, failureMessage)
|
||||
} catch {
|
||||
saveError.value = 'Network error - could not upload PDF'
|
||||
saveError.value = networkErrorMessage
|
||||
return false
|
||||
} finally {
|
||||
isUploadingPdf.value = false
|
||||
isUploadingFile.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +255,10 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
return true
|
||||
}
|
||||
|
||||
async function errorMessage(response: Response, fallbackMessage: string): Promise<string> {
|
||||
async function errorMessage(
|
||||
response: Response,
|
||||
fallbackMessage: string,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const data: ErrorResponse = await response.json()
|
||||
return data.error ?? fallbackMessage
|
||||
|
|
@ -219,13 +274,16 @@ export const useElementsStore = defineStore('elements', () => {
|
|||
error,
|
||||
isSaving,
|
||||
isUploadingIconImage,
|
||||
isUploadingPdf,
|
||||
isUploadingShortPdf,
|
||||
isUploadingLongPdf,
|
||||
saveError,
|
||||
fetchElement,
|
||||
updateElement,
|
||||
uploadElementIconImage,
|
||||
uploadElementPdf,
|
||||
uploadElementShortPdf,
|
||||
uploadElementLongPdf,
|
||||
clearElementIconImage,
|
||||
clearElementPdf,
|
||||
clearElementShortPdf,
|
||||
clearElementLongPdf,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,13 +14,23 @@ interface ElementForm {
|
|||
|
||||
const route = useRoute()
|
||||
const elementsStore = useElementsStore()
|
||||
const { element, isLoading, error, isSaving, isUploadingIconImage, isUploadingPdf, saveError } =
|
||||
storeToRefs(elementsStore)
|
||||
const {
|
||||
element,
|
||||
isLoading,
|
||||
error,
|
||||
isSaving,
|
||||
isUploadingIconImage,
|
||||
isUploadingShortPdf,
|
||||
isUploadingLongPdf,
|
||||
saveError,
|
||||
} = storeToRefs(elementsStore)
|
||||
const savedMessage = ref<string | null>(null)
|
||||
const iconImageStatus = ref<string | null>(null)
|
||||
const pdfStatus = ref<string | null>(null)
|
||||
const shortPdfStatus = ref<string | null>(null)
|
||||
const longPdfStatus = ref<string | null>(null)
|
||||
const iconImageInput = ref<HTMLInputElement | null>(null)
|
||||
const pdfInput = ref<HTMLInputElement | null>(null)
|
||||
const shortPdfInput = ref<HTMLInputElement | null>(null)
|
||||
const longPdfInput = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const form = reactive<ElementForm>({
|
||||
title: '',
|
||||
|
|
@ -56,7 +66,8 @@ watch(
|
|||
|
||||
savedMessage.value = null
|
||||
iconImageStatus.value = null
|
||||
pdfStatus.value = null
|
||||
shortPdfStatus.value = null
|
||||
longPdfStatus.value = null
|
||||
void elementsStore.fetchElement(currentElementId)
|
||||
},
|
||||
{ immediate: true },
|
||||
|
|
@ -95,8 +106,12 @@ function chooseIconImage(): void {
|
|||
iconImageInput.value?.click()
|
||||
}
|
||||
|
||||
function choosePdf(): void {
|
||||
pdfInput.value?.click()
|
||||
function chooseShortPdf(): void {
|
||||
shortPdfInput.value?.click()
|
||||
}
|
||||
|
||||
function chooseLongPdf(): void {
|
||||
longPdfInput.value?.click()
|
||||
}
|
||||
|
||||
async function handleIconImageChange(changeEvent: Event): Promise<void> {
|
||||
|
|
@ -107,7 +122,10 @@ async function handleIconImageChange(changeEvent: Event): Promise<void> {
|
|||
|
||||
savedMessage.value = null
|
||||
iconImageStatus.value = null
|
||||
const uploaded = await elementsStore.uploadElementIconImage(elementId.value, selectedFile)
|
||||
const uploaded = await elementsStore.uploadElementIconImage(
|
||||
elementId.value,
|
||||
selectedFile,
|
||||
)
|
||||
resetInput(changeEvent)
|
||||
|
||||
if (uploaded) {
|
||||
|
|
@ -115,19 +133,41 @@ async function handleIconImageChange(changeEvent: Event): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function handlePdfChange(changeEvent: Event): Promise<void> {
|
||||
async function handleShortPdfChange(changeEvent: Event): Promise<void> {
|
||||
const selectedFile = selectedInputFile(changeEvent)
|
||||
if (selectedFile === null || elementId.value === '') {
|
||||
return
|
||||
}
|
||||
|
||||
savedMessage.value = null
|
||||
pdfStatus.value = null
|
||||
const uploaded = await elementsStore.uploadElementPdf(elementId.value, selectedFile)
|
||||
shortPdfStatus.value = null
|
||||
const uploaded = await elementsStore.uploadElementShortPdf(
|
||||
elementId.value,
|
||||
selectedFile,
|
||||
)
|
||||
resetInput(changeEvent)
|
||||
|
||||
if (uploaded) {
|
||||
pdfStatus.value = 'PDF updated'
|
||||
shortPdfStatus.value = 'Short PDF updated'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLongPdfChange(changeEvent: Event): Promise<void> {
|
||||
const selectedFile = selectedInputFile(changeEvent)
|
||||
if (selectedFile === null || elementId.value === '') {
|
||||
return
|
||||
}
|
||||
|
||||
savedMessage.value = null
|
||||
longPdfStatus.value = null
|
||||
const uploaded = await elementsStore.uploadElementLongPdf(
|
||||
elementId.value,
|
||||
selectedFile,
|
||||
)
|
||||
resetInput(changeEvent)
|
||||
|
||||
if (uploaded) {
|
||||
longPdfStatus.value = 'Long PDF updated'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,16 +184,29 @@ async function handleRemoveIconImage(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleRemovePdf(): Promise<void> {
|
||||
async function handleRemoveShortPdf(): Promise<void> {
|
||||
if (elementId.value === '') {
|
||||
return
|
||||
}
|
||||
|
||||
savedMessage.value = null
|
||||
pdfStatus.value = null
|
||||
const removed = await elementsStore.clearElementPdf(elementId.value)
|
||||
shortPdfStatus.value = null
|
||||
const removed = await elementsStore.clearElementShortPdf(elementId.value)
|
||||
if (removed) {
|
||||
pdfStatus.value = 'PDF removed'
|
||||
shortPdfStatus.value = 'Short PDF removed'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveLongPdf(): Promise<void> {
|
||||
if (elementId.value === '') {
|
||||
return
|
||||
}
|
||||
|
||||
savedMessage.value = null
|
||||
longPdfStatus.value = null
|
||||
const removed = await elementsStore.clearElementLongPdf(elementId.value)
|
||||
if (removed) {
|
||||
longPdfStatus.value = 'Long PDF removed'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +239,9 @@ function resetInput(changeEvent: Event): void {
|
|||
<h1 class="admin-element-page__heading">Edit Element</h1>
|
||||
</header>
|
||||
|
||||
<p v-if="isLoading" class="admin-element-page__status">Loading element...</p>
|
||||
<p v-if="isLoading" class="admin-element-page__status">
|
||||
Loading element...
|
||||
</p>
|
||||
<p
|
||||
v-else-if="error"
|
||||
class="admin-element-page__status admin-element-page__status--error"
|
||||
|
|
@ -194,7 +249,11 @@ function resetInput(changeEvent: Event): void {
|
|||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<form v-else-if="element" class="admin-element-page__form" @submit.prevent="handleSubmit">
|
||||
<form
|
||||
v-else-if="element"
|
||||
class="admin-element-page__form"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<label class="admin-element-page__field">
|
||||
<span class="admin-element-page__label">Title</span>
|
||||
<input
|
||||
|
|
@ -255,7 +314,10 @@ function resetInput(changeEvent: Event): void {
|
|||
</div>
|
||||
<p
|
||||
v-if="iconImageStatus !== null"
|
||||
class="admin-element-page__status admin-element-page__status--inline"
|
||||
:class="[
|
||||
'admin-element-page__status',
|
||||
'admin-element-page__status--inline',
|
||||
]"
|
||||
data-cy="admin-element-icon-image-status"
|
||||
aria-live="polite"
|
||||
>
|
||||
|
|
@ -274,53 +336,110 @@ function resetInput(changeEvent: Event): void {
|
|||
</label>
|
||||
|
||||
<section class="admin-element-page__media-field">
|
||||
<span class="admin-element-page__label">PDF</span>
|
||||
<span class="admin-element-page__label">Short PDF</span>
|
||||
<a
|
||||
v-if="element.pdfPath !== null"
|
||||
:href="element.pdfPath"
|
||||
v-if="element.shortPdfPath !== null"
|
||||
:href="element.shortPdfPath"
|
||||
class="admin-element-page__pdf-link"
|
||||
data-cy="admin-element-current-pdf"
|
||||
data-cy="admin-element-current-short-pdf"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View current PDF
|
||||
View current short PDF
|
||||
</a>
|
||||
<p v-else class="admin-element-page__empty-media">No PDF</p>
|
||||
<p v-else class="admin-element-page__empty-media">No short PDF</p>
|
||||
<div class="admin-element-page__media-actions">
|
||||
<input
|
||||
ref="pdfInput"
|
||||
ref="shortPdfInput"
|
||||
class="admin-element-page__file-input"
|
||||
data-cy="admin-element-pdf-input"
|
||||
data-cy="admin-element-short-pdf-input"
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
:disabled="isUploadingPdf"
|
||||
@change="handlePdfChange"
|
||||
:disabled="isUploadingShortPdf"
|
||||
@change="handleShortPdfChange"
|
||||
/>
|
||||
<button
|
||||
class="admin-element-page__secondary-button"
|
||||
type="button"
|
||||
:disabled="isUploadingPdf"
|
||||
@click="choosePdf"
|
||||
:disabled="isUploadingShortPdf"
|
||||
@click="chooseShortPdf"
|
||||
>
|
||||
{{ isUploadingPdf ? 'Uploading...' : 'Choose PDF' }}
|
||||
{{ isUploadingShortPdf ? 'Uploading...' : 'Choose short PDF' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="element.pdfPath !== null"
|
||||
v-if="element.shortPdfPath !== null"
|
||||
class="admin-element-page__secondary-button"
|
||||
type="button"
|
||||
:disabled="isSaving || isUploadingPdf"
|
||||
@click="handleRemovePdf"
|
||||
:disabled="isSaving || isUploadingShortPdf"
|
||||
@click="handleRemoveShortPdf"
|
||||
>
|
||||
Remove PDF
|
||||
Remove short PDF
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
v-if="pdfStatus !== null"
|
||||
class="admin-element-page__status admin-element-page__status--inline"
|
||||
data-cy="admin-element-pdf-status"
|
||||
v-if="shortPdfStatus !== null"
|
||||
:class="[
|
||||
'admin-element-page__status',
|
||||
'admin-element-page__status--inline',
|
||||
]"
|
||||
data-cy="admin-element-short-pdf-status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ pdfStatus }}
|
||||
{{ shortPdfStatus }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="admin-element-page__media-field">
|
||||
<span class="admin-element-page__label">Long PDF</span>
|
||||
<a
|
||||
v-if="element.longPdfPath !== null"
|
||||
:href="element.longPdfPath"
|
||||
class="admin-element-page__pdf-link"
|
||||
data-cy="admin-element-current-long-pdf"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View current long PDF
|
||||
</a>
|
||||
<p v-else class="admin-element-page__empty-media">No long PDF</p>
|
||||
<div class="admin-element-page__media-actions">
|
||||
<input
|
||||
ref="longPdfInput"
|
||||
class="admin-element-page__file-input"
|
||||
data-cy="admin-element-long-pdf-input"
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
:disabled="isUploadingLongPdf"
|
||||
@change="handleLongPdfChange"
|
||||
/>
|
||||
<button
|
||||
class="admin-element-page__secondary-button"
|
||||
type="button"
|
||||
:disabled="isUploadingLongPdf"
|
||||
@click="chooseLongPdf"
|
||||
>
|
||||
{{ isUploadingLongPdf ? 'Uploading...' : 'Choose long PDF' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="element.longPdfPath !== null"
|
||||
class="admin-element-page__secondary-button"
|
||||
type="button"
|
||||
:disabled="isSaving || isUploadingLongPdf"
|
||||
@click="handleRemoveLongPdf"
|
||||
>
|
||||
Remove long PDF
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
v-if="longPdfStatus !== null"
|
||||
:class="[
|
||||
'admin-element-page__status',
|
||||
'admin-element-page__status--inline',
|
||||
]"
|
||||
data-cy="admin-element-long-pdf-status"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ longPdfStatus }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
|
@ -345,7 +464,10 @@ function resetInput(changeEvent: Event): void {
|
|||
</button>
|
||||
<p
|
||||
v-if="statusMessage !== null"
|
||||
:class="['admin-element-page__status', 'admin-element-page__status--inline']"
|
||||
:class="[
|
||||
'admin-element-page__status',
|
||||
'admin-element-page__status--inline',
|
||||
]"
|
||||
data-cy="admin-element-status"
|
||||
aria-live="polite"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -242,15 +242,29 @@ function isShortYoutubeHost(hostname: string): boolean {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="element.pdfPath !== null" class="element-page__actions">
|
||||
<div
|
||||
v-if="element.shortPdfPath !== null || element.longPdfPath !== null"
|
||||
class="element-page__actions"
|
||||
>
|
||||
<a
|
||||
:href="element.pdfPath"
|
||||
v-if="element.shortPdfPath !== null"
|
||||
:href="element.shortPdfPath"
|
||||
class="element-page__pdf-link"
|
||||
data-cy="element-pdf-link"
|
||||
data-cy="element-short-pdf-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View PDF
|
||||
View Short PDF
|
||||
</a>
|
||||
<a
|
||||
v-if="element.longPdfPath !== null"
|
||||
:href="element.longPdfPath"
|
||||
class="element-page__pdf-link"
|
||||
data-cy="element-long-pdf-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View Long PDF
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -360,7 +374,9 @@ function isShortYoutubeHost(hostname: string): boolean {
|
|||
|
||||
.element-page__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue