diff --git a/backend/app/Controllers/SetController.php b/backend/app/Controllers/SetController.php index a3c89f6..37b846f 100644 --- a/backend/app/Controllers/SetController.php +++ b/backend/app/Controllers/SetController.php @@ -4,15 +4,10 @@ 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\DeleteSet\DeleteSet; -use App\Set\UseCases\DeleteSet\DeleteSetRequest; -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; @@ -24,8 +19,6 @@ class SetController private SetRepository $setRepository, private ElementRepository $elementRepository, private CreateSetWithRoot $createSetWithRoot, - private UpdateSet $updateSet, - private DeleteSet $deleteSet, private Filesystem $filesystem, ) { } @@ -68,54 +61,6 @@ 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); - } - - public function delete(?int $id): JsonResponse - { - try { - $this->deleteSet->execute(new DeleteSetRequest(id: $id)); - } catch (BadRequestException $exception) { - return new JsonResponse([ - 'error' => $exception->getMessage(), - ], 400); - } catch (NotFoundException $exception) { - return new JsonResponse([ - 'error' => $exception->getMessage(), - ], 404); - } - - return new JsonResponse(null, 204); - } - /** * @return array{ * id: int, diff --git a/backend/app/Set/EloquentSetRepository.php b/backend/app/Set/EloquentSetRepository.php index be7ac1d..d7db606 100644 --- a/backend/app/Set/EloquentSetRepository.php +++ b/backend/app/Set/EloquentSetRepository.php @@ -2,8 +2,6 @@ namespace App\Set; -use DomainException; - class EloquentSetRepository implements SetRepository { public function create(CreateSetDto $dto): Set @@ -17,35 +15,6 @@ 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 1557ed4..bf44461 100644 --- a/backend/app/Set/Set.php +++ b/backend/app/Set/Set.php @@ -22,28 +22,13 @@ 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 79d9dde..7f1efcd 100644 --- a/backend/app/Set/SetRepository.php +++ b/backend/app/Set/SetRepository.php @@ -6,10 +6,6 @@ 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/DeleteSet/DeleteSet.php b/backend/app/Set/UseCases/DeleteSet/DeleteSet.php deleted file mode 100644 index 9ff51a7..0000000 --- a/backend/app/Set/UseCases/DeleteSet/DeleteSet.php +++ /dev/null @@ -1,115 +0,0 @@ - - */ - private array $managedFilePaths = []; - - public function __construct( - private SetRepository $setRepository, - private ElementRepository $elementRepository, - private Filesystem $filesystem, - ) { - } - - /** - * @throws BadRequestException - * @throws NotFoundException - */ - public function execute(DeleteSetRequest $request): void - { - if ($request->id === null) { - throw new BadRequestException('setId is required'); - } - - $set = $this->setRepository->find($request->id); - if ($set === null) { - throw new NotFoundException('Set not found'); - } - - $this->managedFilePaths = []; - $this->rememberManagedFile($set->getIconImageUrl()); - - $elements = $this->elementRepository->findBySet($set); - foreach ($elements as $element) { - $this->rememberManagedFile($element->getIconImageUrl()); - $this->rememberManagedFile($element->getShortPdfPath()); - $this->rememberManagedFile($element->getLongPdfPath()); - } - - foreach ($this->deepestElementsFirst($elements) as $element) { - $this->elementRepository->delete($element); - } - - $this->setRepository->delete($set); - foreach (array_keys($this->managedFilePaths) as $path) { - $this->filesystem->delete($path); - } - } - - /** - * @param Element[] $elements - * @return Element[] - */ - private function deepestElementsFirst(array $elements): array - { - usort($elements, function ( - Element $firstElement, - Element $secondElement, - ): int { - $firstDepth = $this->elementDepth($firstElement); - $secondDepth = $this->elementDepth($secondElement); - if ($firstDepth === $secondDepth) { - return $secondElement->getId() <=> $firstElement->getId(); - } - - return $secondDepth <=> $firstDepth; - }); - - return $elements; - } - - private function elementDepth(Element $element): int - { - $depth = 0; - $parentElement = $element->getParentElement(); - while ($parentElement !== null) { - $depth++; - $parentElement = $parentElement->getParentElement(); - } - - return $depth; - } - - private function rememberManagedFile(?string $path): void - { - if ($path === null) { - return; - } - - if (! $this->isManagedFilePath($path)) { - return; - } - - $this->managedFilePaths[$path] = true; - } - - private function isManagedFilePath(string $path): bool - { - return str_starts_with($path, 'set-icons/') - || str_starts_with($path, 'element-icons/') - || str_starts_with($path, 'element-pdfs/short/') - || str_starts_with($path, 'element-pdfs/long/'); - } -} diff --git a/backend/app/Set/UseCases/DeleteSet/DeleteSetRequest.php b/backend/app/Set/UseCases/DeleteSet/DeleteSetRequest.php deleted file mode 100644 index 5954a0c..0000000 --- a/backend/app/Set/UseCases/DeleteSet/DeleteSetRequest.php +++ /dev/null @@ -1,10 +0,0 @@ -id === null) { - throw new BadRequestException('setId is required'); - } - - if ( - $request->description === null - || trim($request->description) === '' - ) { - throw new BadRequestException('description is required'); - } - - $set = $this->setRepository->find($request->id); - if ($set === null) { - throw new NotFoundException('Set not found'); - } - - $set->setDescription($request->description); - - return $this->setRepository->update($set); - } -} diff --git a/backend/app/Set/UseCases/UpdateSet/UpdateDescriptionRequest.php b/backend/app/Set/UseCases/UpdateSet/UpdateDescriptionRequest.php deleted file mode 100644 index 1f975f0..0000000 --- a/backend/app/Set/UseCases/UpdateSet/UpdateDescriptionRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('setId is required'); - } - - if ( - $request->fileContents === null - || $request->fileOriginalName === null - || $request->fileMimeType === null - || $request->fileSizeBytes === null - ) { - throw new BadRequestException('icon image is required'); - } - - if ( - ! in_array( - $request->fileMimeType, - self::ALLOWED_MIME_TYPES, - true, - ) - ) { - throw new BadRequestException( - 'icon image must be a jpeg, png or webp image', - ); - } - - if ($request->fileSizeBytes > self::MAX_SIZE_BYTES) { - throw new BadRequestException( - 'icon image must be 5MB or smaller', - ); - } - - $set = $this->setRepository->find($request->id); - if ($set === null) { - throw new NotFoundException('Set not found'); - } - - $oldIconImageUrl = $set->getIconImageUrl(); - $iconImage = new FileToUpload( - contents: $request->fileContents, - originalName: $request->fileOriginalName, - mimeType: $request->fileMimeType, - sizeBytes: $request->fileSizeBytes, - ); - $path = $this->filesystem->upload($iconImage, 'set-icons'); - $set->setIconImageUrl($path); - $updatedSet = $this->setRepository->update($set); - $this->deleteManagedSetIcon($oldIconImageUrl); - - return $updatedSet; - } - - 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/UpdateIconImageRequest.php b/backend/app/Set/UseCases/UpdateSet/UpdateIconImageRequest.php deleted file mode 100644 index 33f7df3..0000000 --- a/backend/app/Set/UseCases/UpdateSet/UpdateIconImageRequest.php +++ /dev/null @@ -1,15 +0,0 @@ -id === null) { - throw new BadRequestException('setId is required'); - } - - if ($request->name === null || trim($request->name) === '') { - throw new BadRequestException('name is required'); - } - - $set = $this->setRepository->find($request->id); - if ($set === null) { - throw new NotFoundException('Set not found'); - } - - $set->setName(trim($request->name)); - - return $this->setRepository->update($set); - } -} diff --git a/backend/app/Set/UseCases/UpdateSet/UpdateNameRequest.php b/backend/app/Set/UseCases/UpdateSet/UpdateNameRequest.php deleted file mode 100644 index aa68a18..0000000 --- a/backend/app/Set/UseCases/UpdateSet/UpdateNameRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('setId is required'); - } - - if ($this->hasNoFields($request)) { - throw new BadRequestException('nothing to update'); - } - - if ($request->name !== null) { - $this->updateName->execute(new UpdateNameRequest( - id: $request->id, - name: $request->name, - )); - } - if ($request->description !== null) { - $this->updateDescription->execute(new UpdateDescriptionRequest( - id: $request->id, - description: $request->description, - )); - } - if ($this->hasIconImageInput($request)) { - $this->updateIconImage->execute(new UpdateIconImageRequest( - id: $request->id, - fileContents: $request->iconImageContents, - fileOriginalName: $request->iconImageOriginalName, - fileMimeType: $request->iconImageMimeType, - fileSizeBytes: $request->iconImageSizeBytes, - )); - } - - $set = $this->setRepository->find($request->id); - if ($set === null) { - throw new NotFoundException('Set not found'); - } - - return $set; - } - - private function hasNoFields(UpdateSetRequest $request): bool - { - return $request->name === null - && $request->description === null - && ! $this->hasIconImageInput($request); - } - - private function hasIconImageInput(UpdateSetRequest $request): bool - { - return $request->iconImageContents !== null - || $request->iconImageOriginalName !== null - || $request->iconImageMimeType !== null - || $request->iconImageSizeBytes !== null; - } -} diff --git a/backend/app/Set/UseCases/UpdateSet/UpdateSetRequest.php b/backend/app/Set/UseCases/UpdateSet/UpdateSetRequest.php deleted file mode 100644 index b6b3cb9..0000000 --- a/backend/app/Set/UseCases/UpdateSet/UpdateSetRequest.php +++ /dev/null @@ -1,17 +0,0 @@ -middleware(AuthMiddleware::class); -Route::post('/sets/{id}/update', [SetController::class, 'update']) - ->middleware(AuthMiddleware::class); -Route::delete('/sets/{id}', [SetController::class, 'delete']) - ->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 f3efc1c..079ea93 100644 --- a/backend/tests/Fakes/FakeSetRepository.php +++ b/backend/tests/Fakes/FakeSetRepository.php @@ -27,19 +27,6 @@ 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])) { diff --git a/backend/tests/Feature/SetsEndpointTest.php b/backend/tests/Feature/SetsEndpointTest.php index b2afe90..8f4af23 100644 --- a/backend/tests/Feature/SetsEndpointTest.php +++ b/backend/tests/Feature/SetsEndpointTest.php @@ -140,153 +140,6 @@ class SetsEndpointTest extends TestCase ); } - public function testUpdateSetRequiresAuthentication(): void - { - $setRepository = app(SetRepository::class); - $set = $setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: '/assets/original-icon.png', - )); - - $response = $this->postJson( - "/api/sets/{$set->getId()}/update", - [ - 'name' => 'Updated Set', - 'description' => 'Updated set description', - ], - ); - - $response->assertUnauthorized(); - $response->assertExactJson([ - 'error' => 'unauthenticated', - ]); - } - - public function testAuthenticatedUpdateSetUpdatesMediaPayloadOnly(): void - { - $setRepository = app(SetRepository::class); - $elementRepository = app(ElementRepository::class); - $this->createSession('valid-token'); - $set = $setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: '/assets/original-icon.png', - )); - $rootElement = $elementRepository->create(new CreateElementDto( - set: $set, - title: 'Original Root', - description: 'Original root description', - iconImageUrl: '/assets/original-icon.png', - richText: '', - shortPdfPath: null, - longPdfPath: null, - youtubeUrl: null, - parentElement: null, - )); - - $response = $this->withCredentials() - ->withUnencryptedCookie('auth_token', 'valid-token') - ->postJson("/api/sets/{$set->getId()}/update", [ - 'name' => 'Updated Set', - 'description' => 'Updated set description', - ]); - - $response->assertOk(); - $response->assertExactJson([ - 'set' => [ - 'id' => $set->getId(), - 'name' => 'Updated Set', - 'description' => 'Updated set description', - 'iconImageUrl' => '/assets/original-icon.png', - 'rootElementId' => $rootElement->getId(), - ], - ]); - - $updatedSet = $setRepository->find($set->getId()); - $this->assertNotNull($updatedSet); - $this->assertSame('Updated Set', $updatedSet->getName()); - $this->assertSame( - 'Updated set description', - $updatedSet->getDescription(), - ); - - $unchangedRootElement = $elementRepository->find( - $rootElement->getId(), - ); - $this->assertNotNull($unchangedRootElement); - $this->assertSame('Original Root', $unchangedRootElement->getTitle()); - $this->assertSame( - 'Original root description', - $unchangedRootElement->getDescription(), - ); - } - - public function testDeleteSetRequiresAuthentication(): void - { - $setRepository = app(SetRepository::class); - $set = $setRepository->create(new CreateSetDto( - name: 'Set to delete', - description: 'Set deletion description', - iconImageUrl: '/assets/delete-icon.png', - )); - - $response = $this->deleteJson("/api/sets/{$set->getId()}"); - - $response->assertUnauthorized(); - $response->assertExactJson([ - 'error' => 'unauthenticated', - ]); - } - - public function testAuthenticatedDeleteSetRemovesSetAndElements(): void - { - $setRepository = app(SetRepository::class); - $elementRepository = app(ElementRepository::class); - $this->createSession('valid-token'); - $set = $setRepository->create(new CreateSetDto( - name: 'Set to delete', - description: 'Set deletion description', - iconImageUrl: 'set-icons/delete-icon.png', - )); - $rootElement = $elementRepository->create(new CreateElementDto( - set: $set, - title: 'Root to delete', - description: 'Root deletion description', - iconImageUrl: 'set-icons/delete-icon.png', - richText: '', - shortPdfPath: null, - longPdfPath: null, - youtubeUrl: null, - parentElement: null, - )); - $childElement = $elementRepository->create(new CreateElementDto( - set: $set, - title: 'Child to delete', - description: 'Child deletion description', - iconImageUrl: 'element-icons/delete-child.png', - richText: '', - shortPdfPath: 'element-pdfs/short/delete-child.pdf', - longPdfPath: 'element-pdfs/long/delete-child.pdf', - youtubeUrl: null, - parentElement: $rootElement, - )); - - $response = $this->withCredentials() - ->withUnencryptedCookie('auth_token', 'valid-token') - ->deleteJson("/api/sets/{$set->getId()}"); - - $response->assertNoContent(); - $this->assertNull($setRepository->find($set->getId())); - $this->assertNull($elementRepository->find($rootElement->getId())); - $this->assertNull($elementRepository->find($childElement->getId())); - $setsResponse = $this->getJson('/api/sets'); - $setsResponse->assertOk(); - $setsResponse->assertJsonMissing([ - 'id' => $set->getId(), - ]); - } - public function testCreateSetRequiresIconImage(): void { $this->createSession('valid-token'); diff --git a/backend/tests/Unit/Set/UseCases/DeleteSetTest.php b/backend/tests/Unit/Set/UseCases/DeleteSetTest.php deleted file mode 100644 index 6c7272f..0000000 --- a/backend/tests/Unit/Set/UseCases/DeleteSetTest.php +++ /dev/null @@ -1,140 +0,0 @@ -setRepository = new FakeSetRepository(); - $this->elementRepository = new FakeElementRepository(); - $this->filesystem = new FakeFilesystem(); - $this->deleteSet = new DeleteSet( - $this->setRepository, - $this->elementRepository, - $this->filesystem, - ); - } - - public function testDeletesSetElementTreeAndManagedFiles(): void - { - $set = $this->createSet('set-icons/set.png'); - $rootElement = $this->createElement( - set: $set, - parentElement: null, - title: 'Root', - iconImageUrl: 'set-icons/set.png', - shortPdfPath: null, - longPdfPath: null, - ); - $childElement = $this->createElement( - set: $set, - parentElement: $rootElement, - title: 'Child', - iconImageUrl: 'element-icons/child.png', - shortPdfPath: 'element-pdfs/short/child.pdf', - longPdfPath: 'element-pdfs/long/child.pdf', - ); - $grandchildElement = $this->createElement( - set: $set, - parentElement: $childElement, - title: 'Grandchild', - iconImageUrl: 'element-icons/grandchild.png', - shortPdfPath: 'element-pdfs/short/grandchild.pdf', - longPdfPath: 'element-pdfs/long/grandchild.pdf', - ); - - $this->deleteSet->execute(new DeleteSetRequest(id: $set->getId())); - - $this->assertNull($this->setRepository->find($set->getId())); - $this->assertNull( - $this->elementRepository->find($rootElement->getId()), - ); - $this->assertNull( - $this->elementRepository->find($childElement->getId()), - ); - $this->assertNull( - $this->elementRepository->find($grandchildElement->getId()), - ); - $this->assertEqualsCanonicalizing([ - 'set-icons/set.png', - 'element-icons/child.png', - 'element-pdfs/short/child.pdf', - 'element-pdfs/long/child.pdf', - 'element-icons/grandchild.png', - 'element-pdfs/short/grandchild.pdf', - 'element-pdfs/long/grandchild.pdf', - ], $this->filesystem->deletedPaths); - $this->assertSame( - count($this->filesystem->deletedPaths), - count(array_unique($this->filesystem->deletedPaths)), - ); - } - - public function testRequiresSetId(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('setId is required'); - - $this->deleteSet->execute(new DeleteSetRequest(id: null)); - } - - public function testThrowsWhenSetIsMissing(): void - { - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('Set not found'); - - $this->deleteSet->execute(new DeleteSetRequest(id: 999)); - } - - private function createSet(string $iconImageUrl): DomainSet - { - return $this->setRepository->create(new CreateSetDto( - name: 'Set', - description: 'Set description', - iconImageUrl: $iconImageUrl, - )); - } - - private function createElement( - DomainSet $set, - ?Element $parentElement, - string $title, - ?string $iconImageUrl, - ?string $shortPdfPath, - ?string $longPdfPath, - ): Element { - return $this->elementRepository->create(new CreateElementDto( - set: $set, - title: $title, - description: "$title description", - iconImageUrl: $iconImageUrl, - richText: '', - shortPdfPath: $shortPdfPath, - longPdfPath: $longPdfPath, - youtubeUrl: null, - parentElement: $parentElement, - )); - } -} diff --git a/backend/tests/Unit/Set/UseCases/UpdateDescriptionTest.php b/backend/tests/Unit/Set/UseCases/UpdateDescriptionTest.php deleted file mode 100644 index ae19d88..0000000 --- a/backend/tests/Unit/Set/UseCases/UpdateDescriptionTest.php +++ /dev/null @@ -1,67 +0,0 @@ -setRepository = new FakeSetRepository(); - } - - public function testUpdatesOnlyDescription(): void - { - $set = $this->createSet(); - $updateDescription = new UpdateDescription($this->setRepository); - - $updatedSet = $updateDescription->execute( - new UpdateDescriptionRequest( - id: $set->getId(), - description: 'Updated set description', - ) - ); - - $this->assertSame( - 'Updated set description', - $updatedSet->getDescription(), - ); - $this->assertSame($set->getName(), $updatedSet->getName()); - $this->assertSame( - $set->getIconImageUrl(), - $updatedSet->getIconImageUrl(), - ); - } - - public function testRejectsBlankDescription(): void - { - $set = $this->createSet(); - $updateDescription = new UpdateDescription($this->setRepository); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('description is required'); - - $updateDescription->execute(new UpdateDescriptionRequest( - id: $set->getId(), - description: '', - )); - } - - private function createSet(): DomainSet - { - return $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - } -} diff --git a/backend/tests/Unit/Set/UseCases/UpdateIconImageTest.php b/backend/tests/Unit/Set/UseCases/UpdateIconImageTest.php deleted file mode 100644 index 4ff30f5..0000000 --- a/backend/tests/Unit/Set/UseCases/UpdateIconImageTest.php +++ /dev/null @@ -1,108 +0,0 @@ -setRepository = new FakeSetRepository(); - $this->filesystem = new FakeFilesystem(); - } - - public function testUpdatesOnlyIconAndDeletesOldIcon(): void - { - $set = $this->createSet(); - $updateIconImage = new UpdateIconImage( - $this->setRepository, - $this->filesystem, - ); - - $updatedSet = $updateIconImage->execute(new UpdateIconImageRequest( - id: $set->getId(), - fileContents: 'image contents', - fileOriginalName: 'updated.png', - fileMimeType: 'image/png', - fileSizeBytes: 13, - )); - - $this->assertSame($set->getName(), $updatedSet->getName()); - $this->assertSame( - $set->getDescription(), - $updatedSet->getDescription(), - ); - $this->assertSame( - 'set-icons/fake-updated.png', - $updatedSet->getIconImageUrl(), - ); - $this->assertCount(1, $this->filesystem->uploads); - $this->assertSame('set-icons', $this->filesystem->uploads[0]['folder']); - $this->assertSame([ - 'set-icons/original.png', - ], $this->filesystem->deletedPaths); - } - - public function testRejectsMissingFile(): void - { - $set = $this->createSet(); - $updateIconImage = new UpdateIconImage( - $this->setRepository, - $this->filesystem, - ); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('icon image is required'); - - $updateIconImage->execute(new UpdateIconImageRequest( - id: $set->getId(), - fileContents: null, - fileOriginalName: null, - fileMimeType: null, - fileSizeBytes: null, - )); - } - - public function testRejectsInvalidMimeType(): void - { - $set = $this->createSet(); - $updateIconImage = new UpdateIconImage( - $this->setRepository, - $this->filesystem, - ); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage( - 'icon image must be a jpeg, png or webp image', - ); - - $updateIconImage->execute(new UpdateIconImageRequest( - id: $set->getId(), - fileContents: 'image contents', - fileOriginalName: 'updated.gif', - fileMimeType: 'image/gif', - fileSizeBytes: 13, - )); - } - - private function createSet(): DomainSet - { - return $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - } -} diff --git a/backend/tests/Unit/Set/UseCases/UpdateNameTest.php b/backend/tests/Unit/Set/UseCases/UpdateNameTest.php deleted file mode 100644 index 342cb38..0000000 --- a/backend/tests/Unit/Set/UseCases/UpdateNameTest.php +++ /dev/null @@ -1,65 +0,0 @@ -setRepository = new FakeSetRepository(); - } - - public function testUpdatesOnlyName(): void - { - $set = $this->createSet(); - $updateName = new UpdateName($this->setRepository); - - $updatedSet = $updateName->execute(new UpdateNameRequest( - id: $set->getId(), - name: 'Updated Set', - )); - - $this->assertSame('Updated Set', $updatedSet->getName()); - $this->assertSame( - $set->getDescription(), - $updatedSet->getDescription(), - ); - $this->assertSame( - $set->getIconImageUrl(), - $updatedSet->getIconImageUrl(), - ); - } - - public function testRejectsBlankName(): void - { - $set = $this->createSet(); - $updateName = new UpdateName($this->setRepository); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('name is required'); - - $updateName->execute(new UpdateNameRequest( - id: $set->getId(), - name: '', - )); - } - - private function createSet(): DomainSet - { - return $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - } -} diff --git a/backend/tests/Unit/Set/UseCases/UpdateSetTest.php b/backend/tests/Unit/Set/UseCases/UpdateSetTest.php deleted file mode 100644 index 0104c61..0000000 --- a/backend/tests/Unit/Set/UseCases/UpdateSetTest.php +++ /dev/null @@ -1,236 +0,0 @@ -setRepository = new FakeSetRepository(); - $this->elementRepository = new FakeElementRepository(); - $this->filesystem = new FakeFilesystem(); - $this->updateSet = new UpdateSet( - new UpdateName($this->setRepository), - new UpdateDescription($this->setRepository), - new UpdateIconImage( - $this->setRepository, - $this->filesystem, - ), - $this->setRepository, - ); - } - - public function testUpdatesSetWithoutChangingRootElement(): void - { - $set = $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - $rootElement = $this->elementRepository->create(new CreateElementDto( - set: $set, - title: 'Original Root', - description: 'Original root description', - iconImageUrl: 'set-icons/original.png', - richText: '', - shortPdfPath: null, - longPdfPath: null, - youtubeUrl: null, - parentElement: null, - )); - - $updatedSet = $this->updateSet->execute(new UpdateSetRequest( - id: $set->getId(), - name: 'Updated Set', - description: 'Updated set description', - iconImageContents: null, - iconImageOriginalName: null, - iconImageMimeType: null, - iconImageSizeBytes: null, - )); - - $this->assertSame('Updated Set', $updatedSet->getName()); - $this->assertSame( - 'Updated set description', - $updatedSet->getDescription(), - ); - $this->assertSame( - 'set-icons/original.png', - $updatedSet->getIconImageUrl(), - ); - - $unchangedRootElement = $this->elementRepository->find( - $rootElement->getId(), - ); - $this->assertNotNull($unchangedRootElement); - $this->assertSame('Original Root', $unchangedRootElement->getTitle()); - $this->assertSame( - 'Original root description', - $unchangedRootElement->getDescription(), - ); - $this->assertSame( - 'set-icons/original.png', - $unchangedRootElement->getIconImageUrl(), - ); - } - - public function testUpdatesOnlySuppliedFields(): void - { - $set = $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - - $updatedSet = $this->updateSet->execute(new UpdateSetRequest( - id: $set->getId(), - name: 'Updated Set', - description: null, - iconImageContents: null, - iconImageOriginalName: null, - iconImageMimeType: null, - iconImageSizeBytes: null, - )); - - $this->assertSame('Updated Set', $updatedSet->getName()); - $this->assertSame( - 'Original set description', - $updatedSet->getDescription(), - ); - $this->assertSame( - 'set-icons/original.png', - $updatedSet->getIconImageUrl(), - ); - } - - public function testReplacesSetIconAndDeletesOldManagedIcon(): void - { - $set = $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - - $updatedSet = $this->updateSet->execute(new UpdateSetRequest( - id: $set->getId(), - name: 'Updated Set', - description: 'Updated set description', - iconImageContents: 'image contents', - iconImageOriginalName: 'updated.png', - iconImageMimeType: 'image/png', - iconImageSizeBytes: 13, - )); - - $this->assertSame( - 'set-icons/fake-updated.png', - $updatedSet->getIconImageUrl(), - ); - $this->assertCount(1, $this->filesystem->uploads); - $this->assertSame('set-icons', $this->filesystem->uploads[0]['folder']); - $this->assertSame([ - 'set-icons/original.png', - ], $this->filesystem->deletedPaths); - } - - public function testRejectsNothingToUpdate(): void - { - $set = $this->createSet(); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('nothing to update'); - - $this->updateSet->execute(new UpdateSetRequest( - id: $set->getId(), - name: null, - description: null, - iconImageContents: null, - iconImageOriginalName: null, - iconImageMimeType: null, - iconImageSizeBytes: null, - )); - } - - public function testRequiresName(): void - { - $set = $this->createSet(); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('name is required'); - - $this->updateSet->execute(new UpdateSetRequest( - id: $set->getId(), - name: '', - description: null, - iconImageContents: null, - iconImageOriginalName: null, - iconImageMimeType: null, - iconImageSizeBytes: null, - )); - } - - public function testRequiresDescription(): void - { - $set = $this->createSet(); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('description is required'); - - $this->updateSet->execute(new UpdateSetRequest( - id: $set->getId(), - name: null, - description: '', - iconImageContents: null, - iconImageOriginalName: null, - iconImageMimeType: null, - iconImageSizeBytes: null, - )); - } - - public function testThrowsWhenSetIsMissing(): void - { - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('Set not found'); - - $this->updateSet->execute(new UpdateSetRequest( - id: 999, - name: 'Updated Set', - description: 'Updated set description', - iconImageContents: null, - iconImageOriginalName: null, - iconImageMimeType: null, - iconImageSizeBytes: null, - )); - } - - private function createSet(): DomainSet - { - return $this->setRepository->create(new CreateSetDto( - name: 'Original Set', - description: 'Original set description', - iconImageUrl: 'set-icons/original.png', - )); - } -} diff --git a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts index 46d81a1..9aca794 100644 --- a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts +++ b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts @@ -181,13 +181,6 @@ describe('media page sets', () => { cy.get('[data-cy="media-create-set-card"]').should('not.exist') }) - it('does not show set management actions to logged-out users', () => { - cy.visit('/media') - - cy.get('[data-cy="media-set-edit"]').should('not.exist') - cy.get('[data-cy="media-set-delete"]').should('not.exist') - }) - it('creates a set from the logged-in media page modal', () => { loginAsAdmin() cy.visit('/media') @@ -212,67 +205,4 @@ describe('media page sets', () => { cy.get('[data-cy="admin-element-description"]') .should('have.value', 'Created through the media page modal') }) - - it('edits a set from the logged-in media page modal', () => { - cy.resetDb() - loginAsAdmin() - cy.visit('/media') - cy.intercept('POST', /\/api\/sets\/1\/update$/).as('updateSet') - - cy.contains('[data-cy="media-set-card"]', 'Baderech HaAvodah') - .within(() => { - cy.get('[data-cy="media-set-edit"]').click() - }) - cy.get('[data-cy="edit-set-modal"]').should('be.visible') - cy.get('[data-cy="edit-set-name"]') - .clear() - .type('Edited Cypress Set') - cy.get('[data-cy="edit-set-description"]') - .clear() - .type('Edited through the set modal') - cy.get('[data-cy="edit-set-submit"]').click() - cy.wait('@updateSet') - - cy.get('[data-cy="edit-set-modal"]').should('not.exist') - cy.contains('[data-cy="media-set-card"]', 'Edited Cypress Set') - .should('be.visible') - .and('contain.text', 'Edited through the set modal') - .click() - cy.location('pathname').should('eq', '/element/1') - cy.contains('h1', 'Baderech HaAvodah').should('be.visible') - - cy.resetDb() - }) - - it('warns before deleting a set from the logged-in media page', () => { - cy.resetDb() - loginAsAdmin() - cy.visit('/media') - cy.intercept('DELETE', /\/api\/sets\/2$/).as('deleteSet') - - cy.contains('[data-cy="media-set-card"]', 'Daily Learning') - .within(() => { - cy.get('[data-cy="media-set-delete"]').click() - }) - cy.get('[data-cy="delete-set-modal"]') - .should('be.visible') - .and('contain.text', 'Daily Learning') - .and('contain.text', 'all elements and uploaded files') - cy.get('[data-cy="delete-set-cancel"]').click() - cy.get('[data-cy="delete-set-modal"]').should('not.exist') - cy.contains('[data-cy="media-set-card"]', 'Daily Learning') - .should('be.visible') - - cy.contains('[data-cy="media-set-card"]', 'Daily Learning') - .within(() => { - cy.get('[data-cy="media-set-delete"]').click() - }) - cy.get('[data-cy="delete-set-confirm"]').click() - cy.wait('@deleteSet') - cy.get('[data-cy="delete-set-modal"]').should('not.exist') - cy.contains('[data-cy="media-set-card"]', 'Daily Learning') - .should('not.exist') - - cy.resetDb() - }) }) diff --git a/frontend/rabbi_gerzi/src/stores/mediaSets.ts b/frontend/rabbi_gerzi/src/stores/mediaSets.ts index e669e85..e869470 100644 --- a/frontend/rabbi_gerzi/src/stores/mediaSets.ts +++ b/frontend/rabbi_gerzi/src/stores/mediaSets.ts @@ -15,18 +15,11 @@ export interface CreateMediaSetInput { iconImage: File } -export interface UpdateMediaSetInput { - id: number - name: string - description: string - iconImage: File | null -} - interface SetsResponse { sets: MediaSet[] } -interface SetResponse { +interface CreateSetResponse { set: MediaSet } @@ -41,11 +34,7 @@ export const useMediaSetsStore = defineStore('mediaSets', () => { const isLoading = ref(false) const error = ref(null) const isCreating = ref(false) - const isUpdating = ref(false) - const isDeleting = ref(false) const createError = ref(null) - const updateError = ref(null) - const deleteError = ref(null) async function fetchSets(): Promise { error.value = null @@ -94,7 +83,7 @@ export const useMediaSetsStore = defineStore('mediaSets', () => { return null } - const data: SetResponse = await response.json() + const data: CreateSetResponse = await response.json() sets.value = [...sets.value, data.set] return data.set @@ -106,112 +95,13 @@ export const useMediaSetsStore = defineStore('mediaSets', () => { } } - async function updateSet( - input: UpdateMediaSetInput, - ): Promise { - updateError.value = null - isUpdating.value = true - - const formData = new FormData() - formData.append('name', input.name) - formData.append('description', input.description) - if (input.iconImage !== null) { - formData.append('iconImage', input.iconImage) - } - - try { - const encodedSetId = encodeURIComponent(input.id.toString()) - const response = await fetch( - `${API_BASE_URL}/api/sets/${encodedSetId}/update`, - { - method: 'POST', - credentials: 'include', - body: formData, - }, - ) - - if (!response.ok) { - updateError.value = await errorMessage( - response, - 'Could not update media set', - ) - return null - } - - const data: SetResponse = await response.json() - sets.value = sets.value.map((mediaSet) => { - if (mediaSet.id === data.set.id) { - return data.set - } - - return mediaSet - }) - - return data.set - } catch { - updateError.value = 'Network error - could not update media set' - return null - } finally { - isUpdating.value = false - } - } - - async function deleteSet(setId: number): Promise { - deleteError.value = null - isDeleting.value = true - - try { - const encodedSetId = encodeURIComponent(setId.toString()) - const response = await fetch(`${API_BASE_URL}/api/sets/${encodedSetId}`, { - method: 'DELETE', - credentials: 'include', - }) - - if (!response.ok) { - deleteError.value = await errorMessage( - response, - 'Could not delete media set', - ) - return false - } - - sets.value = sets.value.filter((mediaSet) => { - return mediaSet.id !== setId - }) - return true - } catch { - deleteError.value = 'Network error - could not delete media set' - return false - } finally { - isDeleting.value = false - } - } - - async function errorMessage( - response: Response, - fallbackMessage: string, - ): Promise { - try { - const data: ErrorResponse = await response.json() - return data.error ?? fallbackMessage - } catch { - return fallbackMessage - } - } - return { sets, isLoading, error, isCreating, - isUpdating, - isDeleting, createError, - updateError, - deleteError, fetchSets, createSet, - updateSet, - deleteSet, } }) diff --git a/frontend/rabbi_gerzi/src/views/MediaPage.vue b/frontend/rabbi_gerzi/src/views/MediaPage.vue index 07f407b..513a329 100644 --- a/frontend/rabbi_gerzi/src/views/MediaPage.vue +++ b/frontend/rabbi_gerzi/src/views/MediaPage.vue @@ -1,17 +1,11 @@