diff --git a/backend/app/Controllers/SetController.php b/backend/app/Controllers/SetController.php index 37b846f..239963f 100644 --- a/backend/app/Controllers/SetController.php +++ b/backend/app/Controllers/SetController.php @@ -4,10 +4,13 @@ namespace App\Controllers; use App\Element\ElementRepository; use App\Exceptions\BadRequestException; +use App\Exceptions\NotFoundException; use App\Set\Set as DomainSet; use App\Set\SetRepository; use App\Set\UseCases\CreateSetWithRoot\CreateSetWithRoot; use App\Set\UseCases\CreateSetWithRoot\CreateSetWithRootRequest; +use App\Set\UseCases\UpdateSet\UpdateSet; +use App\Set\UseCases\UpdateSet\UpdateSetRequest; use App\Shared\Files\Filesystem; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -19,6 +22,7 @@ class SetController private SetRepository $setRepository, private ElementRepository $elementRepository, private CreateSetWithRoot $createSetWithRoot, + private UpdateSet $updateSet, private Filesystem $filesystem, ) { } @@ -61,6 +65,37 @@ class SetController ], 201); } + public function update(Request $request, ?int $id): JsonResponse + { + $iconImage = $this->uploadedFileInput($request, 'iconImage'); + + try { + $set = $this->updateSet->execute( + new UpdateSetRequest( + id: $id, + name: $this->stringInput($request, 'name'), + description: $this->stringInput($request, 'description'), + iconImageContents: $iconImage['contents'], + iconImageOriginalName: $iconImage['originalName'], + iconImageMimeType: $iconImage['mimeType'], + iconImageSizeBytes: $iconImage['sizeBytes'], + ) + ); + } catch (BadRequestException $exception) { + return new JsonResponse([ + 'error' => $exception->getMessage(), + ], 400); + } catch (NotFoundException $exception) { + return new JsonResponse([ + 'error' => $exception->getMessage(), + ], 404); + } + + return new JsonResponse([ + 'set' => $this->buildSetPayload($set), + ], 200); + } + /** * @return array{ * id: int, diff --git a/backend/app/Set/EloquentSetRepository.php b/backend/app/Set/EloquentSetRepository.php index d7db606..be7ac1d 100644 --- a/backend/app/Set/EloquentSetRepository.php +++ b/backend/app/Set/EloquentSetRepository.php @@ -2,6 +2,8 @@ namespace App\Set; +use DomainException; + class EloquentSetRepository implements SetRepository { public function create(CreateSetDto $dto): Set @@ -15,6 +17,35 @@ class EloquentSetRepository implements SetRepository return $this->toDomain($model); } + public function update(Set $set): Set + { + $model = SetModel::find($set->getId()); + if ($model === null) { + throw new DomainException( + "Set with id: {$set->getId()} doesnt exist" + ); + } + + $model->name = $set->getName(); + $model->description = $set->getDescription(); + $model->icon_image_url = $set->getIconImageUrl(); + $model->save(); + + return $this->toDomain($model); + } + + public function delete(Set $set): void + { + $model = SetModel::find($set->getId()); + if ($model === null) { + throw new DomainException( + "Set with id: {$set->getId()} doesnt exist" + ); + } + + $model->delete(); + } + public function find(int $id): ?Set { $model = SetModel::find($id); diff --git a/backend/app/Set/Set.php b/backend/app/Set/Set.php index bf44461..1557ed4 100644 --- a/backend/app/Set/Set.php +++ b/backend/app/Set/Set.php @@ -22,13 +22,28 @@ class Set return $this->name; } + public function setName(string $name): void + { + $this->name = $name; + } + public function getDescription(): string { return $this->description; } + public function setDescription(string $description): void + { + $this->description = $description; + } + public function getIconImageUrl(): string { return $this->iconImageUrl; } + + public function setIconImageUrl(string $iconImageUrl): void + { + $this->iconImageUrl = $iconImageUrl; + } } diff --git a/backend/app/Set/SetRepository.php b/backend/app/Set/SetRepository.php index 7f1efcd..79d9dde 100644 --- a/backend/app/Set/SetRepository.php +++ b/backend/app/Set/SetRepository.php @@ -6,6 +6,10 @@ interface SetRepository { public function create(CreateSetDto $dto): Set; + public function update(Set $set): Set; + + public function delete(Set $set): void; + public function find(int $id): ?Set; /** diff --git a/backend/app/Set/UseCases/UpdateSet/UpdateSet.php b/backend/app/Set/UseCases/UpdateSet/UpdateSet.php new file mode 100644 index 0000000..19c6bc4 --- /dev/null +++ b/backend/app/Set/UseCases/UpdateSet/UpdateSet.php @@ -0,0 +1,128 @@ +id === null) { + throw new BadRequestException('setId is required'); + } + if ($request->name === null || $request->name === '') { + throw new BadRequestException('name is required'); + } + if ( + $request->description === null + || $request->description === '' + ) { + throw new BadRequestException('description is required'); + } + + $set = $this->setRepository->find($request->id); + if ($set === null) { + throw new NotFoundException('Set not found'); + } + + $oldIconImageUrl = $set->getIconImageUrl(); + $newIconImageUrl = $this->iconImageUrl($request, $oldIconImageUrl); + + $set->setName($request->name); + $set->setDescription($request->description); + $set->setIconImageUrl($newIconImageUrl); + + $updatedSet = $this->setRepository->update($set); + if ($newIconImageUrl !== $oldIconImageUrl) { + $this->deleteManagedSetIcon($oldIconImageUrl); + } + + return $updatedSet; + } + + /** + * @throws BadRequestException + */ + private function iconImageUrl( + UpdateSetRequest $request, + string $currentIconImageUrl, + ): string { + if ( + $request->iconImageContents === null + && $request->iconImageOriginalName === null + && $request->iconImageMimeType === null + && $request->iconImageSizeBytes === null + ) { + return $currentIconImageUrl; + } + + if ( + $request->iconImageContents === null + || $request->iconImageOriginalName === null + || $request->iconImageMimeType === null + || $request->iconImageSizeBytes === null + ) { + throw new BadRequestException('icon image is required'); + } + + if ( + ! in_array( + $request->iconImageMimeType, + self::ALLOWED_MIME_TYPES, + true, + ) + ) { + throw new BadRequestException( + 'icon image must be a jpeg, png or webp image', + ); + } + + if ($request->iconImageSizeBytes > self::MAX_SIZE_BYTES) { + throw new BadRequestException( + 'icon image must be 5MB or smaller', + ); + } + + $iconImage = new FileToUpload( + contents: $request->iconImageContents, + originalName: $request->iconImageOriginalName, + mimeType: $request->iconImageMimeType, + sizeBytes: $request->iconImageSizeBytes, + ); + + return $this->filesystem->upload($iconImage, 'set-icons'); + } + + private function deleteManagedSetIcon(string $path): void + { + if (! str_starts_with($path, 'set-icons/')) { + return; + } + + $this->filesystem->delete($path); + } +} diff --git a/backend/app/Set/UseCases/UpdateSet/UpdateSetRequest.php b/backend/app/Set/UseCases/UpdateSet/UpdateSetRequest.php new file mode 100644 index 0000000..b6b3cb9 --- /dev/null +++ b/backend/app/Set/UseCases/UpdateSet/UpdateSetRequest.php @@ -0,0 +1,17 @@ +middleware(AuthMiddleware::class); +Route::post('/sets/{id}/update', [SetController::class, 'update']) + ->middleware(AuthMiddleware::class); Route::get('/elements/{id}', [ElementController::class, 'show']); Route::get('/element-pdfs/{folder}/{fileName}', [ ElementController::class, diff --git a/backend/tests/Fakes/FakeSetRepository.php b/backend/tests/Fakes/FakeSetRepository.php index 079ea93..f3efc1c 100644 --- a/backend/tests/Fakes/FakeSetRepository.php +++ b/backend/tests/Fakes/FakeSetRepository.php @@ -27,6 +27,19 @@ class FakeSetRepository implements SetRepository return $set; } + public function update(DomainSet $set): DomainSet + { + $updatedSet = $this->cloneSet($set); + $this->setsById[$updatedSet->getId()] = $updatedSet; + + return $this->cloneSet($updatedSet); + } + + public function delete(DomainSet $set): void + { + unset($this->setsById[$set->getId()]); + } + public function find(int $id): ?DomainSet { if (! isset($this->setsById[$id])) {