From f9c92f3206841f2516aa2530e282f7afae24fa26 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:46:44 +0300 Subject: [PATCH 01/10] test element endpoint --- .../tests/Feature/ElementsEndpointTest.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 backend/tests/Feature/ElementsEndpointTest.php diff --git a/backend/tests/Feature/ElementsEndpointTest.php b/backend/tests/Feature/ElementsEndpointTest.php new file mode 100644 index 0000000..1683cf0 --- /dev/null +++ b/backend/tests/Feature/ElementsEndpointTest.php @@ -0,0 +1,41 @@ +create(new CreateSetDto( + name: 'Baderech HaAvodah', + description: 'A structured path for growth', + iconImageUrl: '/assets/baderech-haavodah-icon.png', + )); + $element = $elementRepository->create(new CreateElementDto( + set: $set, + title: 'Baderech HaAvodah', + parentElement: null, + )); + + $response = $this->getJson("/api/elements/{$element->getId()}"); + + $response->assertOk(); + $response->assertExactJson([ + 'element' => [ + 'id' => $element->getId(), + 'title' => 'Baderech HaAvodah', + ], + ]); + } +} From 90de724f6379a316233261ad66bf1a5673ac3615 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:47:17 +0300 Subject: [PATCH 02/10] add element endpoint --- backend/app/Controllers/ElementController.php | 25 +++++++++++++++++++ backend/routes/api.php | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 backend/app/Controllers/ElementController.php diff --git a/backend/app/Controllers/ElementController.php b/backend/app/Controllers/ElementController.php new file mode 100644 index 0000000..7d23213 --- /dev/null +++ b/backend/app/Controllers/ElementController.php @@ -0,0 +1,25 @@ +elementRepository->find($id); + + return new JsonResponse([ + 'element' => [ + 'id' => $element->getId(), + 'title' => $element->getTitle(), + ], + ], 200); + } +} diff --git a/backend/routes/api.php b/backend/routes/api.php index 92cd42d..f32b2ca 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,6 +1,7 @@ middleware(AuthMiddleware::class); Route::get('/sets', [SetController::class, 'index']); +Route::get('/elements/{id}', [ElementController::class, 'show']); From 1b05d4f1f5343a0293fa5b3087b4dbbad57a2459 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:47:38 +0300 Subject: [PATCH 03/10] test missing element --- backend/tests/Feature/ElementsEndpointTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/tests/Feature/ElementsEndpointTest.php b/backend/tests/Feature/ElementsEndpointTest.php index 1683cf0..be69aec 100644 --- a/backend/tests/Feature/ElementsEndpointTest.php +++ b/backend/tests/Feature/ElementsEndpointTest.php @@ -38,4 +38,14 @@ class ElementsEndpointTest extends TestCase ], ]); } + + public function testReturns404WhenElementDoesNotExist(): void + { + $response = $this->getJson('/api/elements/999'); + + $response->assertNotFound(); + $response->assertExactJson([ + 'error' => 'Element not found', + ]); + } } From 5e1ec0dd73963ddb5a7e385e797a80e2bb43546c Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:47:58 +0300 Subject: [PATCH 04/10] handle missing element --- backend/app/Controllers/ElementController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/app/Controllers/ElementController.php b/backend/app/Controllers/ElementController.php index 7d23213..9e61720 100644 --- a/backend/app/Controllers/ElementController.php +++ b/backend/app/Controllers/ElementController.php @@ -14,6 +14,11 @@ class ElementController public function show(int $id): JsonResponse { $element = $this->elementRepository->find($id); + if ($element === null) { + return new JsonResponse([ + 'error' => 'Element not found', + ], 404); + } return new JsonResponse([ 'element' => [ From 8029a9e157b535d9b2fef1077560c6a99771a3c0 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:48:50 +0300 Subject: [PATCH 05/10] test element title display --- frontend/rabbi_gerzi/cypress/e2e/media.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts index 2a093a0..69fe661 100644 --- a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts +++ b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts @@ -40,6 +40,6 @@ describe('media page sets', () => { cy.get('@baderechCard').click() cy.location('pathname').should('eq', '/element/1') cy.get('[data-cy="element-page"]').should('be.visible') - cy.contains('h1', 'Element 1').should('be.visible') + cy.contains('h1', 'Baderech HaAvodah').should('be.visible') }) }) From ba6bd357e21fa25a9c755688ec0bcafeeab4dcee Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:51:45 +0300 Subject: [PATCH 06/10] fetch element title --- frontend/rabbi_gerzi/src/stores/elements.ts | 50 +++++++++++++++++++ .../rabbi_gerzi/src/views/ElementPage.vue | 46 ++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 frontend/rabbi_gerzi/src/stores/elements.ts diff --git a/frontend/rabbi_gerzi/src/stores/elements.ts b/frontend/rabbi_gerzi/src/stores/elements.ts new file mode 100644 index 0000000..0624751 --- /dev/null +++ b/frontend/rabbi_gerzi/src/stores/elements.ts @@ -0,0 +1,50 @@ +import { ref } from 'vue' +import { defineStore } from 'pinia' + +export interface Element { + id: number + title: string +} + +interface ElementResponse { + element: Element +} + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string + +export const useElementsStore = defineStore('elements', () => { + const element = ref(null) + const isLoading = ref(false) + const error = ref(null) + + async function fetchElement(elementId: string): Promise { + element.value = null + error.value = null + isLoading.value = true + + try { + const encodedElementId = encodeURIComponent(elementId) + const elementUrl = `${API_BASE_URL}/api/elements/${encodedElementId}` + const response = await fetch(elementUrl) + + if (response.status === 404) { + error.value = 'Element not found' + return + } + + if (!response.ok) { + error.value = 'Could not load element' + return + } + + const data: ElementResponse = await response.json() + element.value = data.element + } catch { + error.value = 'Network error - could not load element' + } finally { + isLoading.value = false + } + } + + return { element, isLoading, error, fetchElement } +}) diff --git a/frontend/rabbi_gerzi/src/views/ElementPage.vue b/frontend/rabbi_gerzi/src/views/ElementPage.vue index 22191ce..c222cef 100644 --- a/frontend/rabbi_gerzi/src/views/ElementPage.vue +++ b/frontend/rabbi_gerzi/src/views/ElementPage.vue @@ -1,9 +1,13 @@ @@ -46,6 +72,22 @@ const elementId = computed(() => { text-align: center; } +.element-page__status { + max-width: 48rem; + margin: 0 auto; + padding: 1rem 1.25rem; + color: var(--color-text-muted); + background: var(--color-white); + border: 1px solid var(--color-border); + border-radius: 8px; + font-size: 0.95rem; +} + +.element-page__status--error { + color: #7c2d2d; + border-color: #e5b8b8; +} + @media (max-width: 768px) { .element-page__main { padding: 3rem 1rem; From ca4d2dad3b4ebe30a1e1f87abe879fc6f30badec Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:58:19 +0300 Subject: [PATCH 07/10] test get element use case --- .../Unit/Element/UseCases/GetElementTest.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 backend/tests/Unit/Element/UseCases/GetElementTest.php diff --git a/backend/tests/Unit/Element/UseCases/GetElementTest.php b/backend/tests/Unit/Element/UseCases/GetElementTest.php new file mode 100644 index 0000000..a301af6 --- /dev/null +++ b/backend/tests/Unit/Element/UseCases/GetElementTest.php @@ -0,0 +1,71 @@ +elementRepo = new FakeElementRepository(); + $this->getElement = new GetElement($this->elementRepo); + } + + public function testReturnsElementWhenFound(): void + { + $element = $this->createElement('Baderech HaAvodah'); + + $foundElement = $this->getElement->execute(new GetElementRequest( + id: $element->getId(), + )); + + $this->assertInstanceOf(Element::class, $foundElement); + $this->assertSame($element->getId(), $foundElement->getId()); + $this->assertSame('Baderech HaAvodah', $foundElement->getTitle()); + } + + public function testThrowsWhenIdMissing(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('id is required'); + + $this->getElement->execute(new GetElementRequest(id: null)); + } + + public function testThrowsWhenElementDoesNotExist(): void + { + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('Element not found'); + + $this->getElement->execute(new GetElementRequest(id: 999)); + } + + private function createElement(string $title): Element + { + $set = new DomainSet( + id: 1, + name: 'Baderech', + description: 'Baderech description', + iconImageUrl: '/assets/baderech-icon.png', + ); + + return $this->elementRepo->create(new CreateElementDto( + set: $set, + title: $title, + parentElement: null, + )); + } +} From 46f5e6138ee479f6350da6324fee11e625638877 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 19:59:09 +0300 Subject: [PATCH 08/10] add get element use case --- .../UseCases/GetElement/GetElement.php | 33 +++++++++++++++++++ .../UseCases/GetElement/GetElementRequest.php | 10 ++++++ backend/app/Exceptions/NotFoundException.php | 9 +++++ 3 files changed, 52 insertions(+) create mode 100644 backend/app/Element/UseCases/GetElement/GetElement.php create mode 100644 backend/app/Element/UseCases/GetElement/GetElementRequest.php create mode 100644 backend/app/Exceptions/NotFoundException.php diff --git a/backend/app/Element/UseCases/GetElement/GetElement.php b/backend/app/Element/UseCases/GetElement/GetElement.php new file mode 100644 index 0000000..2dc555e --- /dev/null +++ b/backend/app/Element/UseCases/GetElement/GetElement.php @@ -0,0 +1,33 @@ +id === null) { + throw new BadRequestException('id is required'); + } + + $element = $this->elementRepository->find($request->id); + if ($element === null) { + throw new NotFoundException('Element not found'); + } + + return $element; + } +} diff --git a/backend/app/Element/UseCases/GetElement/GetElementRequest.php b/backend/app/Element/UseCases/GetElement/GetElementRequest.php new file mode 100644 index 0000000..0e636b6 --- /dev/null +++ b/backend/app/Element/UseCases/GetElement/GetElementRequest.php @@ -0,0 +1,10 @@ + Date: Tue, 26 May 2026 19:59:55 +0300 Subject: [PATCH 09/10] test element controller mapping --- .../Controllers/ElementControllerTest.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 backend/tests/Unit/Controllers/ElementControllerTest.php diff --git a/backend/tests/Unit/Controllers/ElementControllerTest.php b/backend/tests/Unit/Controllers/ElementControllerTest.php new file mode 100644 index 0000000..93dddfc --- /dev/null +++ b/backend/tests/Unit/Controllers/ElementControllerTest.php @@ -0,0 +1,75 @@ +elementRepo = new FakeElementRepository(); + $getElement = new GetElement($this->elementRepo); + $this->controller = new ElementController($getElement); + } + + public function testShowReturnsElementPayload(): void + { + $element = $this->createElement('Baderech HaAvodah'); + + $response = $this->controller->show($element->getId()); + + $this->assertEquals(200, $response->getStatusCode()); + $body = json_decode($response->getContent(), true); + $this->assertSame($element->getId(), $body['element']['id']); + $this->assertSame('Baderech HaAvodah', $body['element']['title']); + } + + public function testShowReturns400WhenIdMissing(): void + { + $response = $this->controller->show(null); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertSame( + ['error' => 'id is required'], + json_decode($response->getContent(), true), + ); + } + + public function testShowReturns404WhenElementDoesNotExist(): void + { + $response = $this->controller->show(999); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertSame( + ['error' => 'Element not found'], + json_decode($response->getContent(), true), + ); + } + + private function createElement(string $title): Element + { + $set = new DomainSet( + id: 1, + name: 'Baderech', + description: 'Baderech description', + iconImageUrl: '/assets/baderech-icon.png', + ); + + return $this->elementRepo->create(new CreateElementDto( + set: $set, + title: $title, + parentElement: null, + )); + } +} From 28ea873f385c861e164343353df097be9b5a13c2 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 26 May 2026 20:00:42 +0300 Subject: [PATCH 10/10] wire element use case --- backend/app/Controllers/ElementController.php | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/app/Controllers/ElementController.php b/backend/app/Controllers/ElementController.php index 9e61720..62a6385 100644 --- a/backend/app/Controllers/ElementController.php +++ b/backend/app/Controllers/ElementController.php @@ -2,21 +2,31 @@ namespace App\Controllers; -use App\Element\ElementRepository; +use App\Element\UseCases\GetElement\GetElement; +use App\Element\UseCases\GetElement\GetElementRequest; +use App\Exceptions\BadRequestException; +use App\Exceptions\NotFoundException; use Illuminate\Http\JsonResponse; class ElementController { - public function __construct(private ElementRepository $elementRepository) + public function __construct(private GetElement $getElement) { } - public function show(int $id): JsonResponse + public function show(?int $id): JsonResponse { - $element = $this->elementRepository->find($id); - if ($element === null) { + try { + $element = $this->getElement->execute( + new GetElementRequest(id: $id) + ); + } catch (BadRequestException $exception) { return new JsonResponse([ - 'error' => 'Element not found', + 'error' => $exception->getMessage(), + ], 400); + } catch (NotFoundException $exception) { + return new JsonResponse([ + 'error' => $exception->getMessage(), ], 404); }