diff --git a/ai/backend-context.md b/ai/backend-context.md index 34729b2..dd69678 100644 --- a/ai/backend-context.md +++ b/ai/backend-context.md @@ -20,9 +20,6 @@ intentionally omitted here - update this section as entities land. - Do not write unit tests for concrete repository implementations (e.g. `Postgres*Repository`). They are exercised by e2e tests. Use cases are tested with fake repositories. - - Do not write reflection-only contract tests for repository interface - method names, parameter counts, parameter names, or type hints. PHP - interfaces already enforce those signatures; test behavior instead. - Repository methods that find records by a foreign key should accept the related entity, not a raw id (e.g. `findBySet(Set $set)`, not `findBySetId(int $setId)`). diff --git a/backend/app/Controllers/ElementController.php b/backend/app/Controllers/ElementController.php index 6a63a85..1c346c6 100644 --- a/backend/app/Controllers/ElementController.php +++ b/backend/app/Controllers/ElementController.php @@ -2,22 +2,16 @@ namespace App\Controllers; -use App\Element\Element; use App\Element\UseCases\GetElement\GetElement; use App\Element\UseCases\GetElement\GetElementRequest; -use App\Element\UseCases\UpdateElement\UpdateElement; -use App\Element\UseCases\UpdateElement\UpdateElementRequest; use App\Exceptions\BadRequestException; use App\Exceptions\NotFoundException; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; class ElementController { - public function __construct( - private GetElement $getElement, - private UpdateElement $updateElement, - ) { + public function __construct(private GetElement $getElement) + { } public function show(?int $id): JsonResponse @@ -48,73 +42,15 @@ class ElementController return new JsonResponse([ 'childElements' => $childElements, - 'element' => $this->buildElementPayload($element), + 'element' => [ + 'id' => $element->getId(), + 'title' => $element->getTitle(), + 'description' => $element->getDescription(), + 'iconImageUrl' => $element->getIconImageUrl(), + 'richText' => $element->getRichText(), + 'pdfPath' => $element->getPdfPath(), + 'youtubeUrl' => $element->getYoutubeUrl(), + ], ], 200); } - - public function update(?int $id, Request $request): JsonResponse - { - try { - $element = $this->updateElement->execute( - new UpdateElementRequest( - id: $id, - title: $this->stringInput($request, 'title'), - description: $this->stringInput($request, 'description'), - iconImageUrl: $this->stringInput( - $request, - 'iconImageUrl', - ), - richText: $this->stringInput($request, 'richText'), - pdfPath: $this->stringInput($request, 'pdfPath'), - youtubeUrl: $this->stringInput($request, 'youtubeUrl'), - ) - ); - } catch (BadRequestException $exception) { - return new JsonResponse([ - 'error' => $exception->getMessage(), - ], 400); - } catch (NotFoundException $exception) { - return new JsonResponse([ - 'error' => $exception->getMessage(), - ], 404); - } - - return new JsonResponse([ - 'element' => $this->buildElementPayload($element), - ], 200); - } - - private function stringInput(Request $request, string $key): ?string - { - $value = $request->input($key); - if (! is_string($value)) { - return null; - } - - return $value; - } - - /** - * @return array{ - * id: int, - * title: string, - * description: string, - * iconImageUrl: string|null, - * richText: string, - * pdfPath: string|null, - * youtubeUrl: string|null, - * } - */ - private function buildElementPayload(Element $element): array - { - return [ - 'id' => $element->getId(), - 'title' => $element->getTitle(), - 'description' => $element->getDescription(), - 'iconImageUrl' => $element->getIconImageUrl(), - 'richText' => $element->getRichText(), - 'pdfPath' => $element->getPdfPath(), - 'youtubeUrl' => $element->getYoutubeUrl(), - ]; - } } diff --git a/backend/app/Element/Element.php b/backend/app/Element/Element.php index 37be364..41fec0d 100644 --- a/backend/app/Element/Element.php +++ b/backend/app/Element/Element.php @@ -29,61 +29,31 @@ class Element return $this->title; } - public function setTitle(string $title): void - { - $this->title = $title; - } - 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; - } - public function getRichText(): string { return $this->richText; } - public function setRichText(string $richText): void - { - $this->richText = $richText; - } - public function getPdfPath(): ?string { return $this->pdfPath; } - public function setPdfPath(?string $pdfPath): void - { - $this->pdfPath = $pdfPath; - } - public function getYoutubeUrl(): ?string { return $this->youtubeUrl; } - public function setYoutubeUrl(?string $youtubeUrl): void - { - $this->youtubeUrl = $youtubeUrl; - } - public function getSet(): Set { return $this->set; diff --git a/backend/app/Element/ElementRepository.php b/backend/app/Element/ElementRepository.php index e4fa74e..3e89628 100644 --- a/backend/app/Element/ElementRepository.php +++ b/backend/app/Element/ElementRepository.php @@ -8,8 +8,6 @@ interface ElementRepository { public function create(CreateElementDto $dto): Element; - public function update(Element $element): Element; - public function find(int $id): ?Element; public function findRootBySet(DomainSet $set): ?Element; diff --git a/backend/app/Element/EloquentElementRepository.php b/backend/app/Element/EloquentElementRepository.php index 379abe0..b8c06d7 100644 --- a/backend/app/Element/EloquentElementRepository.php +++ b/backend/app/Element/EloquentElementRepository.php @@ -38,28 +38,6 @@ class EloquentElementRepository implements ElementRepository ); } - public function update(Element $element): Element - { - $model = ElementModel::find($element->getId()); - if ($model === null) { - throw new DomainException( - "Element with id: {$element->getId()} doesnt exist" - ); - } - - $model->set_id = $element->getSet()->getId(); - $model->title = $element->getTitle(); - $model->description = $element->getDescription(); - $model->icon_image_url = $element->getIconImageUrl(); - $model->rich_text = $element->getRichText(); - $model->pdf_path = $element->getPdfPath(); - $model->youtube_url = $element->getYoutubeUrl(); - $model->parent_element_id = $element->getParentElement()?->getId(); - $model->save(); - - return $this->toDomain($model); - } - public function find(int $id): ?Element { $model = ElementModel::find($id); diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateDescription.php b/backend/app/Element/UseCases/UpdateElement/UpdateDescription.php deleted file mode 100644 index 6e277b9..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateDescription.php +++ /dev/null @@ -1,39 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - if ($request->description === null) { - throw new BadRequestException('description is required'); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - $element->setDescription($request->description); - - return $this->elementRepository->update($element); - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateDescriptionRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdateDescriptionRequest.php deleted file mode 100644 index 882d4ff..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateDescriptionRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - $hasNoFields = $request->title === null - && $request->description === null - && $request->iconImageUrl === null - && $request->richText === null - && $request->pdfPath === null - && $request->youtubeUrl === null; - if ($hasNoFields) { - throw new BadRequestException('nothing to update'); - } - - if ($request->title !== null) { - $this->updateTitle->execute(new UpdateTitleRequest( - id: $request->id, - title: $request->title, - )); - } - if ($request->description !== null) { - $this->updateDescription->execute(new UpdateDescriptionRequest( - id: $request->id, - description: $request->description, - )); - } - if ($request->iconImageUrl !== null) { - $this->updateIconImageUrl->execute( - new UpdateIconImageUrlRequest( - id: $request->id, - iconImageUrl: $request->iconImageUrl, - ) - ); - } - if ($request->richText !== null) { - $this->updateRichText->execute(new UpdateRichTextRequest( - id: $request->id, - richText: $request->richText, - )); - } - if ($request->pdfPath !== null) { - $this->updatePdfPath->execute(new UpdatePdfPathRequest( - id: $request->id, - pdfPath: $request->pdfPath, - )); - } - if ($request->youtubeUrl !== null) { - $this->updateYoutubeUrl->execute(new UpdateYoutubeUrlRequest( - id: $request->id, - youtubeUrl: $request->youtubeUrl, - )); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - return $element; - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateElementRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdateElementRequest.php deleted file mode 100644 index 0b5e766..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateElementRequest.php +++ /dev/null @@ -1,17 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - if ($request->iconImageUrl === null) { - throw new BadRequestException('iconImageUrl is required'); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - $element->setIconImageUrl($this->nullableString( - $request->iconImageUrl, - )); - - return $this->elementRepository->update($element); - } - - private function nullableString(string $value): ?string - { - if ($value === '') { - return null; - } - - return $value; - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateIconImageUrlRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdateIconImageUrlRequest.php deleted file mode 100644 index d328af3..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateIconImageUrlRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - if ($request->pdfPath === null) { - throw new BadRequestException('pdfPath is required'); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - $element->setPdfPath($this->nullableString($request->pdfPath)); - - return $this->elementRepository->update($element); - } - - private function nullableString(string $value): ?string - { - if ($value === '') { - return null; - } - - return $value; - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdatePdfPathRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdatePdfPathRequest.php deleted file mode 100644 index c48c47d..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdatePdfPathRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - if ($request->richText === null) { - throw new BadRequestException('richText is required'); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - $element->setRichText($request->richText); - - return $this->elementRepository->update($element); - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateRichTextRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdateRichTextRequest.php deleted file mode 100644 index fe29972..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateRichTextRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - if ($request->title === null || trim($request->title) === '') { - throw new BadRequestException('title is required'); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - $element->setTitle(trim($request->title)); - - return $this->elementRepository->update($element); - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateTitleRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdateTitleRequest.php deleted file mode 100644 index 5a13ea9..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateTitleRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -id === null) { - throw new BadRequestException('id is required'); - } - - if ($request->youtubeUrl === null) { - throw new BadRequestException('youtubeUrl is required'); - } - - $element = $this->elementRepository->find($request->id); - if ($element === null) { - throw new NotFoundException('Element not found'); - } - - $element->setYoutubeUrl($this->nullableString( - $request->youtubeUrl, - )); - - return $this->elementRepository->update($element); - } - - private function nullableString(string $value): ?string - { - if ($value === '') { - return null; - } - - return $value; - } -} diff --git a/backend/app/Element/UseCases/UpdateElement/UpdateYoutubeUrlRequest.php b/backend/app/Element/UseCases/UpdateElement/UpdateYoutubeUrlRequest.php deleted file mode 100644 index 3a7e06a..0000000 --- a/backend/app/Element/UseCases/UpdateElement/UpdateYoutubeUrlRequest.php +++ /dev/null @@ -1,12 +0,0 @@ - ['api/*'], - 'allowed_methods' => ['GET', 'POST', 'PATCH', 'OPTIONS'], + 'allowed_methods' => ['GET', 'POST', 'OPTIONS'], 'allowed_origins' => $allowedOrigins, 'allowed_origins_patterns' => [], 'allowed_headers' => [ diff --git a/backend/routes/api.php b/backend/routes/api.php index 9d253d0..f32b2ca 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -12,5 +12,3 @@ Route::get('/me', [AuthController::class, 'me']) ->middleware(AuthMiddleware::class); Route::get('/sets', [SetController::class, 'index']); Route::get('/elements/{id}', [ElementController::class, 'show']); -Route::patch('/elements/{id}', [ElementController::class, 'update']) - ->middleware(AuthMiddleware::class); diff --git a/backend/tests/Fakes/FakeElementRepository.php b/backend/tests/Fakes/FakeElementRepository.php index 9c08e1c..132a526 100644 --- a/backend/tests/Fakes/FakeElementRepository.php +++ b/backend/tests/Fakes/FakeElementRepository.php @@ -33,14 +33,6 @@ class FakeElementRepository implements ElementRepository return $element; } - public function update(Element $element): Element - { - $updatedElement = $this->cloneElement($element); - $this->elementsById[$updatedElement->getId()] = $updatedElement; - - return $this->cloneElement($updatedElement); - } - public function find(int $id): ?Element { if (! isset($this->elementsById[$id])) { diff --git a/backend/tests/Feature/CorsTest.php b/backend/tests/Feature/CorsTest.php index 52273e4..b5ab81d 100644 --- a/backend/tests/Feature/CorsTest.php +++ b/backend/tests/Feature/CorsTest.php @@ -21,23 +21,4 @@ class CorsTest extends TestCase ); $response->assertHeader('Access-Control-Allow-Credentials', 'true'); } - - public function testAllowsPatchPreflight(): void - { - $response = $this->withHeaders([ - 'Origin' => 'http://localhost:5173', - 'Access-Control-Request-Method' => 'PATCH', - 'Access-Control-Request-Headers' => 'content-type', - ])->options('/api/elements/1'); - - $response->assertNoContent(); - $response->assertHeader( - 'Access-Control-Allow-Origin', - 'http://localhost:5173' - ); - $this->assertStringContainsString( - 'PATCH', - $response->headers->get('Access-Control-Allow-Methods') - ); - } } diff --git a/backend/tests/Feature/ElementsEndpointTest.php b/backend/tests/Feature/ElementsEndpointTest.php index 2ff9089..33725c8 100644 --- a/backend/tests/Feature/ElementsEndpointTest.php +++ b/backend/tests/Feature/ElementsEndpointTest.php @@ -2,17 +2,10 @@ namespace Tests\Feature; -use App\Auth\CreateSessionDto; -use App\Auth\SessionRepository; use App\Element\CreateElementDto; use App\Element\ElementRepository; use App\Set\CreateSetDto; use App\Set\SetRepository; -use App\Shared\ValueObject\EmailAddress; -use App\User\CreateUserDto; -use App\User\UserRepository; -use DateTimeImmutable; -use DateTimeZone; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -99,116 +92,4 @@ class ElementsEndpointTest extends TestCase 'error' => 'Element not found', ]); } - - public function testUpdateRequiresAuthentication(): void - { - $setRepository = app(SetRepository::class); - $elementRepository = app(ElementRepository::class); - $set = $setRepository->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', - description: 'A structured path for growth', - iconImageUrl: '/assets/baderech-haavodah-icon.png', - richText: '

A structured path for growth

', - pdfPath: '/assets/pdfs/baderech.pdf', - youtubeUrl: null, - parentElement: null, - )); - - $response = $this->patchJson("/api/elements/{$element->getId()}", [ - 'title' => 'Updated title', - 'description' => 'Updated description', - 'iconImageUrl' => '/assets/updated-icon.png', - 'richText' => '

Updated rich text

', - 'pdfPath' => '/assets/pdfs/updated.pdf', - 'youtubeUrl' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', - ]); - - $response->assertUnauthorized(); - $response->assertExactJson([ - 'error' => 'unauthenticated', - ]); - } - - public function testAuthenticatedUpdateReturnsElementPayload(): void - { - $sampleYoutubeUrl = 'https://www.youtube.com/watch?v=' - . 'dQw4w9WgXcQ'; - $setRepository = app(SetRepository::class); - $elementRepository = app(ElementRepository::class); - $set = $setRepository->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', - description: 'A structured path for growth', - iconImageUrl: '/assets/baderech-haavodah-icon.png', - richText: '

A structured path for growth

', - pdfPath: '/assets/pdfs/baderech.pdf', - youtubeUrl: null, - parentElement: null, - )); - $this->createSession('valid-token'); - - $response = $this->withCredentials() - ->withUnencryptedCookie('auth_token', 'valid-token') - ->patchJson("/api/elements/{$element->getId()}", [ - 'title' => 'Updated title', - 'description' => 'Updated description', - 'iconImageUrl' => '/assets/updated-icon.png', - 'richText' => '

Updated rich text

', - 'pdfPath' => '/assets/pdfs/updated.pdf', - 'youtubeUrl' => $sampleYoutubeUrl, - ]); - - $response->assertOk(); - $response->assertExactJson([ - 'element' => [ - 'id' => $element->getId(), - 'title' => 'Updated title', - 'description' => 'Updated description', - 'iconImageUrl' => '/assets/updated-icon.png', - 'richText' => '

Updated rich text

', - 'pdfPath' => '/assets/pdfs/updated.pdf', - 'youtubeUrl' => $sampleYoutubeUrl, - ], - ]); - $updatedElement = $elementRepository->find($element->getId()); - $this->assertSame('Updated title', $updatedElement->getTitle()); - $this->assertSame( - '

Updated rich text

', - $updatedElement->getRichText(), - ); - } - - private function createSession(string $token): void - { - $userRepository = app(UserRepository::class); - $sessionRepository = app(SessionRepository::class); - $user = $userRepository->create(new CreateUserDto( - email: new EmailAddress('admin@example.com'), - passwordHash: 'password-hash', - )); - $now = new DateTimeImmutable( - '2026-05-01T00:00:00', - new DateTimeZone('UTC'), - ); - $sessionRepository->create(new CreateSessionDto( - token: $token, - user: $user, - createdAt: $now, - expiresAt: new DateTimeImmutable( - '2099-01-01T00:00:00', - new DateTimeZone('UTC'), - ), - )); - } } diff --git a/backend/tests/Unit/Controllers/ElementControllerTest.php b/backend/tests/Unit/Controllers/ElementControllerTest.php index 99bfa3e..d06c0f7 100644 --- a/backend/tests/Unit/Controllers/ElementControllerTest.php +++ b/backend/tests/Unit/Controllers/ElementControllerTest.php @@ -6,13 +6,6 @@ use App\Controllers\ElementController; use App\Element\CreateElementDto; use App\Element\Element; use App\Element\UseCases\GetElement\GetElement; -use App\Element\UseCases\UpdateElement\UpdateDescription; -use App\Element\UseCases\UpdateElement\UpdateElement; -use App\Element\UseCases\UpdateElement\UpdateIconImageUrl; -use App\Element\UseCases\UpdateElement\UpdatePdfPath; -use App\Element\UseCases\UpdateElement\UpdateRichText; -use App\Element\UseCases\UpdateElement\UpdateTitle; -use App\Element\UseCases\UpdateElement\UpdateYoutubeUrl; use App\Set\Set as DomainSet; use Tests\Fakes\FakeElementRepository; use Tests\TestCase; @@ -27,16 +20,7 @@ class ElementControllerTest extends TestCase { $this->elementRepo = new FakeElementRepository(); $getElement = new GetElement($this->elementRepo); - $updateElement = new UpdateElement( - new UpdateTitle($this->elementRepo), - new UpdateDescription($this->elementRepo), - new UpdateIconImageUrl($this->elementRepo), - new UpdateRichText($this->elementRepo), - new UpdatePdfPath($this->elementRepo), - new UpdateYoutubeUrl($this->elementRepo), - $this->elementRepo, - ); - $this->controller = new ElementController($getElement, $updateElement); + $this->controller = new ElementController($getElement); } public function testShowReturnsElementPayload(): void diff --git a/backend/tests/Unit/Element/ElementTest.php b/backend/tests/Unit/Element/ElementTest.php index 89e9c84..16c08a7 100644 --- a/backend/tests/Unit/Element/ElementTest.php +++ b/backend/tests/Unit/Element/ElementTest.php @@ -68,39 +68,4 @@ class ElementTest extends TestCase $this->assertNull($rootElement->getYoutubeUrl()); $this->assertNull($rootElement->getParentElement()); } - - public function testUpdatesEditableFields(): void - { - $set = new DomainSet( - id: 1, - name: 'Daily learning', - description: 'Daily learning description', - iconImageUrl: '/assets/daily-learning-icon.svg', - ); - $element = new Element( - id: 1, - title: 'Original', - description: 'Original description', - iconImageUrl: '/assets/original.svg', - richText: '

Original

', - pdfPath: '/assets/pdfs/original.pdf', - youtubeUrl: 'https://www.youtube.com/watch?v=yHx-r4p6hHU', - set: $set, - parentElement: null, - ); - - $element->setTitle('Updated'); - $element->setDescription('Updated description'); - $element->setIconImageUrl(null); - $element->setRichText('

Updated

'); - $element->setPdfPath(null); - $element->setYoutubeUrl(null); - - $this->assertSame('Updated', $element->getTitle()); - $this->assertSame('Updated description', $element->getDescription()); - $this->assertNull($element->getIconImageUrl()); - $this->assertSame('

Updated

', $element->getRichText()); - $this->assertNull($element->getPdfPath()); - $this->assertNull($element->getYoutubeUrl()); - } } diff --git a/backend/tests/Unit/Element/UseCases/UpdateElementFieldsTest.php b/backend/tests/Unit/Element/UseCases/UpdateElementFieldsTest.php deleted file mode 100644 index 7c27c9c..0000000 --- a/backend/tests/Unit/Element/UseCases/UpdateElementFieldsTest.php +++ /dev/null @@ -1,175 +0,0 @@ -elementRepo = new FakeElementRepository(); - } - - public function testUpdateTitleUpdatesOnlyTitle(): void - { - $element = $this->createFilledElement(); - $updateTitle = new UpdateTitle($this->elementRepo); - - $updatedElement = $updateTitle->execute(new UpdateTitleRequest( - id: $element->getId(), - title: 'Updated title', - )); - - $this->assertSame('Updated title', $updatedElement->getTitle()); - $this->assertSame( - $element->getDescription(), - $updatedElement->getDescription(), - ); - $this->assertSame( - $element->getRichText(), - $updatedElement->getRichText(), - ); - } - - public function testUpdateTitleRejectsBlankTitle(): void - { - $this->createFilledElement(); - $updateTitle = new UpdateTitle($this->elementRepo); - - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('title is required'); - - $updateTitle->execute(new UpdateTitleRequest(id: 1, title: '')); - } - - public function testUpdateDescriptionUpdatesOnlyDescription(): void - { - $element = $this->createFilledElement(); - $updateDescription = new UpdateDescription($this->elementRepo); - - $updatedElement = $updateDescription->execute( - new UpdateDescriptionRequest( - id: $element->getId(), - description: 'Updated description', - ) - ); - - $this->assertSame( - 'Updated description', - $updatedElement->getDescription(), - ); - $this->assertSame($element->getTitle(), $updatedElement->getTitle()); - $this->assertSame( - $element->getYoutubeUrl(), - $updatedElement->getYoutubeUrl(), - ); - } - - public function testUpdateIconImageUrlClearsEmptyString(): void - { - $element = $this->createFilledElement(); - $updateIconImageUrl = new UpdateIconImageUrl($this->elementRepo); - - $updatedElement = $updateIconImageUrl->execute( - new UpdateIconImageUrlRequest( - id: $element->getId(), - iconImageUrl: '', - ) - ); - - $this->assertNull($updatedElement->getIconImageUrl()); - $this->assertSame($element->getTitle(), $updatedElement->getTitle()); - } - - public function testUpdateRichTextUpdatesOnlyRichText(): void - { - $element = $this->createFilledElement(); - $updateRichText = new UpdateRichText($this->elementRepo); - - $updatedElement = $updateRichText->execute( - new UpdateRichTextRequest( - id: $element->getId(), - richText: '

Updated rich text

', - ) - ); - - $this->assertSame( - '

Updated rich text

', - $updatedElement->getRichText(), - ); - $this->assertSame( - $element->getDescription(), - $updatedElement->getDescription(), - ); - } - - public function testUpdatePdfPathClearsEmptyString(): void - { - $element = $this->createFilledElement(); - $updatePdfPath = new UpdatePdfPath($this->elementRepo); - - $updatedElement = $updatePdfPath->execute( - new UpdatePdfPathRequest(id: $element->getId(), pdfPath: '') - ); - - $this->assertNull($updatedElement->getPdfPath()); - $this->assertSame($element->getTitle(), $updatedElement->getTitle()); - } - - public function testUpdateYoutubeUrlClearsEmptyString(): void - { - $element = $this->createFilledElement(); - $updateYoutubeUrl = new UpdateYoutubeUrl($this->elementRepo); - - $updatedElement = $updateYoutubeUrl->execute( - new UpdateYoutubeUrlRequest( - id: $element->getId(), - youtubeUrl: '', - ) - ); - - $this->assertNull($updatedElement->getYoutubeUrl()); - $this->assertSame($element->getTitle(), $updatedElement->getTitle()); - } - - private function createFilledElement(): 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: 'Original title', - description: 'Original description', - iconImageUrl: '/assets/original-icon.png', - richText: '

Original rich text

', - pdfPath: '/assets/pdfs/original.pdf', - youtubeUrl: 'https://www.youtube.com/watch?v=yHx-r4p6hHU', - parentElement: null, - )); - } -} diff --git a/backend/tests/Unit/Element/UseCases/UpdateElementTest.php b/backend/tests/Unit/Element/UseCases/UpdateElementTest.php deleted file mode 100644 index 0ce0d88..0000000 --- a/backend/tests/Unit/Element/UseCases/UpdateElementTest.php +++ /dev/null @@ -1,270 +0,0 @@ -elementRepo = new FakeElementRepository(); - $this->updateElement = new UpdateElement( - new UpdateTitle($this->elementRepo), - new UpdateDescription($this->elementRepo), - new UpdateIconImageUrl($this->elementRepo), - new UpdateRichText($this->elementRepo), - new UpdatePdfPath($this->elementRepo), - new UpdateYoutubeUrl($this->elementRepo), - $this->elementRepo, - ); - } - - public function testUpdatesContentFields(): void - { - $set = $this->createSet(1, 'Baderech'); - $element = $this->createElement( - $set, - 'Original title', - 'Original description', - '/assets/original-icon.png', - '

Original rich text

', - '/assets/pdfs/original.pdf', - 'https://www.youtube.com/watch?v=yHx-r4p6hHU&t=1s', - null, - ); - - $updatedElement = $this->updateElement->execute( - new UpdateElementRequest( - id: $element->getId(), - title: 'Updated title', - description: 'Updated description', - iconImageUrl: '/assets/updated-icon.png', - richText: '

Updated rich text

', - pdfPath: '/assets/pdfs/updated.pdf', - youtubeUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', - ) - ); - - $this->assertSame($element->getId(), $updatedElement->getId()); - $this->assertSame('Updated title', $updatedElement->getTitle()); - $this->assertSame( - 'Updated description', - $updatedElement->getDescription(), - ); - $this->assertSame( - '/assets/updated-icon.png', - $updatedElement->getIconImageUrl(), - ); - $this->assertSame( - '

Updated rich text

', - $updatedElement->getRichText(), - ); - $this->assertSame( - '/assets/pdfs/updated.pdf', - $updatedElement->getPdfPath(), - ); - $this->assertSame( - 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', - $updatedElement->getYoutubeUrl(), - ); - - $persistedElement = $this->elementRepo->find($element->getId()); - $this->assertInstanceOf(Element::class, $persistedElement); - $this->assertSame('Updated title', $persistedElement->getTitle()); - } - - public function testConvertsEmptyNullableFieldsToNull(): void - { - $set = $this->createSet(1, 'Baderech'); - $element = $this->createElement( - $set, - 'Original title', - 'Original description', - '/assets/original-icon.png', - '

Original rich text

', - '/assets/pdfs/original.pdf', - 'https://www.youtube.com/watch?v=yHx-r4p6hHU&t=1s', - null, - ); - - $updatedElement = $this->updateElement->execute( - new UpdateElementRequest( - id: $element->getId(), - title: 'Updated title', - description: 'Updated description', - iconImageUrl: '', - richText: '

Updated rich text

', - pdfPath: '', - youtubeUrl: '', - ) - ); - - $this->assertNull($updatedElement->getIconImageUrl()); - $this->assertNull($updatedElement->getPdfPath()); - $this->assertNull($updatedElement->getYoutubeUrl()); - } - - public function testUpdatesOnlySuppliedFields(): void - { - $set = $this->createSet(1, 'Baderech'); - $element = $this->createElement( - $set, - 'Original title', - 'Original description', - '/assets/original-icon.png', - '

Original rich text

', - '/assets/pdfs/original.pdf', - 'https://www.youtube.com/watch?v=yHx-r4p6hHU&t=1s', - null, - ); - - $updatedElement = $this->updateElement->execute( - new UpdateElementRequest( - id: $element->getId(), - title: 'Updated title', - description: null, - iconImageUrl: null, - richText: null, - pdfPath: null, - youtubeUrl: null, - ) - ); - - $this->assertSame('Updated title', $updatedElement->getTitle()); - $this->assertSame( - 'Original description', - $updatedElement->getDescription(), - ); - $this->assertSame( - '/assets/original-icon.png', - $updatedElement->getIconImageUrl(), - ); - $this->assertSame( - '

Original rich text

', - $updatedElement->getRichText(), - ); - $this->assertSame( - '/assets/pdfs/original.pdf', - $updatedElement->getPdfPath(), - ); - $this->assertSame( - 'https://www.youtube.com/watch?v=yHx-r4p6hHU&t=1s', - $updatedElement->getYoutubeUrl(), - ); - } - - public function testThrowsWhenIdMissing(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('id is required'); - - $this->updateElement->execute(new UpdateElementRequest( - id: null, - title: 'Updated title', - description: 'Updated description', - iconImageUrl: null, - richText: '

Updated rich text

', - pdfPath: null, - youtubeUrl: null, - )); - } - - public function testThrowsWhenNothingProvided(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('nothing to update'); - - $this->updateElement->execute(new UpdateElementRequest( - id: 1, - title: null, - description: null, - iconImageUrl: null, - richText: null, - pdfPath: null, - youtubeUrl: null, - )); - } - - public function testThrowsWhenTitleIsBlank(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('title is required'); - - $this->updateElement->execute(new UpdateElementRequest( - id: 1, - title: '', - description: 'Updated description', - iconImageUrl: null, - richText: '

Updated rich text

', - pdfPath: null, - youtubeUrl: null, - )); - } - - public function testThrowsWhenElementDoesNotExist(): void - { - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage('Element not found'); - - $this->updateElement->execute(new UpdateElementRequest( - id: 999, - title: 'Updated title', - description: 'Updated description', - iconImageUrl: null, - richText: '

Updated rich text

', - pdfPath: null, - youtubeUrl: null, - )); - } - - private function createSet(int $id, string $name): DomainSet - { - return new DomainSet( - id: $id, - name: $name, - description: "$name description", - iconImageUrl: '/assets/baderech-icon.png', - ); - } - - private function createElement( - DomainSet $set, - string $title, - string $description, - ?string $iconImageUrl, - string $richText, - ?string $pdfPath, - ?string $youtubeUrl, - ?Element $parentElement, - ): Element { - return $this->elementRepo->create(new CreateElementDto( - set: $set, - title: $title, - description: $description, - iconImageUrl: $iconImageUrl, - richText: $richText, - pdfPath: $pdfPath, - youtubeUrl: $youtubeUrl, - parentElement: $parentElement, - )); - } -} diff --git a/frontend/rabbi_gerzi/cypress.config.mjs b/frontend/rabbi_gerzi/cypress.config.mjs new file mode 100644 index 0000000..d215a17 --- /dev/null +++ b/frontend/rabbi_gerzi/cypress.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig } from 'cypress' +import createBundler from '@bahmutov/cypress-esbuild-preprocessor' + +export default defineConfig({ + e2e: { + baseUrl: 'http://localhost:5173', + specPattern: 'cypress/e2e/**/*.cy.ts', + supportFile: 'cypress/support/e2e.ts', + fixturesFolder: false, + video: false, + screenshotOnRunFailure: false, + setupNodeEvents(onEvent) { + onEvent('file:preprocessor', createBundler()) + }, + }, +}) diff --git a/frontend/rabbi_gerzi/cypress.config.ts b/frontend/rabbi_gerzi/cypress.config.ts deleted file mode 100644 index 82a9470..0000000 --- a/frontend/rabbi_gerzi/cypress.config.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { execSync } from 'node:child_process' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' -import { defineConfig } from 'cypress' -import createBundler from '@bahmutov/cypress-esbuild-preprocessor' -import { Client } from 'pg' - -const configDirectory = dirname(fileURLToPath(import.meta.url)) -const backendDirectory = resolve(configDirectory, '../../backend') -const socketDirectory = resolve(configDirectory, '../../.postgres') -const appDatabase = 'postgres' -const templateDatabase = 'postgres_cypress_template' - -function controlClient(): Client { - return new Client({ - host: socketDirectory, - database: 'template1', - user: 'postgres', - }) -} - -async function buildTemplate(): Promise { - const setupClient = controlClient() - await setupClient.connect() - await setupClient.query( - `UPDATE pg_database SET datistemplate = false - WHERE datname = $1`, - [templateDatabase], - ) - await setupClient.query( - `SELECT pg_terminate_backend(pid) FROM pg_stat_activity - WHERE datname = $1 AND pid <> pg_backend_pid()`, - [templateDatabase], - ) - await setupClient.query(`DROP DATABASE IF EXISTS ${templateDatabase}`) - await setupClient.query(`CREATE DATABASE ${templateDatabase}`) - await setupClient.end() - - const seedEnvironment = { ...process.env, DB_DATABASE: templateDatabase } - execSync('php artisan migrate:fresh --force', { - cwd: backendDirectory, - stdio: 'pipe', - env: seedEnvironment, - }) - execSync('php artisan db:seed --force', { - cwd: backendDirectory, - stdio: 'pipe', - env: seedEnvironment, - }) - - const markClient = controlClient() - await markClient.connect() - await markClient.query( - `UPDATE pg_database SET datistemplate = true - WHERE datname = $1`, - [templateDatabase], - ) - await markClient.end() -} - -async function resetFromTemplate(): Promise { - const client = controlClient() - await client.connect() - await client.query( - `SELECT pg_terminate_backend(pid) FROM pg_stat_activity - WHERE datname IN ($1, $2) AND pid <> pg_backend_pid()`, - [appDatabase, templateDatabase], - ) - await client.query(`DROP DATABASE IF EXISTS ${appDatabase}`) - await client.query( - `CREATE DATABASE ${appDatabase} - TEMPLATE ${templateDatabase}`, - ) - await client.end() - - return null -} - -export default defineConfig({ - e2e: { - baseUrl: 'http://localhost:5173', - specPattern: 'cypress/e2e/**/*.cy.ts', - supportFile: 'cypress/support/e2e.ts', - fixturesFolder: false, - video: false, - screenshotOnRunFailure: false, - setupNodeEvents(onEvent) { - onEvent('before:run', async function () { - await buildTemplate() - }) - onEvent('file:preprocessor', createBundler()) - onEvent('task', { - 'db:reset': resetFromTemplate, - }) - }, - }, -}) diff --git a/frontend/rabbi_gerzi/cypress/e2e/admin-element.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/admin-element.cy.ts deleted file mode 100644 index 8678ec2..0000000 --- a/frontend/rabbi_gerzi/cypress/e2e/admin-element.cy.ts +++ /dev/null @@ -1,123 +0,0 @@ -function loginAsAdmin(): void { - cy.visit('/login') - - cy.get('[data-cy="login-email"]').type('admin@rabbigerzi.test') - cy.get('[data-cy="login-password"]').type('password123!@#') - cy.get('[data-cy="login-submit"]').click() - - cy.url().should('eq', Cypress.config().baseUrl + '/') - cy.getCookie('auth_token').should('exist') -} - -function fillElementForm( - title: string, - description: string, - iconImageUrl: string, - richText: string, - pdfPath: string, - youtubeUrl: string, -): void { - cy.get('[data-cy="admin-element-title"]').clear().type(title) - cy.get('[data-cy="admin-element-description"]').clear().type(description) - cy.get('[data-cy="admin-element-icon-image-url"]') - .clear() - .type(iconImageUrl) - cy.get('[data-cy="admin-element-rich-text"]').clear() - if (richText !== '') { - cy.get('[data-cy="admin-element-rich-text"]').type(richText, { - parseSpecialCharSequences: false, - }) - } - cy.get('[data-cy="admin-element-pdf-path"]').clear() - if (pdfPath !== '') { - cy.get('[data-cy="admin-element-pdf-path"]').type(pdfPath) - } - cy.get('[data-cy="admin-element-youtube-url"]').clear() - if (youtubeUrl !== '') { - cy.get('[data-cy="admin-element-youtube-url"]').type(youtubeUrl) - } -} - -describe('admin element editing', () => { - it('does not show the edit link to logged-out users', () => { - cy.visit('/element/1') - - cy.contains('header.site-header a', 'Edit Element').should('not.exist') - }) - - it('shows the edit link on public element pages to logged-in users', () => { - loginAsAdmin() - - cy.visit('/element/1') - - cy.contains('header.site-header a', 'Edit Element') - .should('be.visible') - .and('have.attr', 'href', '/admin/element/1') - .click() - cy.location('pathname').should('eq', '/admin/element/1') - }) - - it('redirects logged-out admin element visits to login', () => { - cy.visit('/admin/element/1') - - cy.location('pathname').should('eq', '/login') - }) - - it('shows the view link on admin element pages', () => { - loginAsAdmin() - - cy.visit('/admin/element/1') - - cy.contains('header.site-header a', 'View Element') - .should('be.visible') - .and('have.attr', 'href', '/element/1') - cy.contains('header.site-header a', 'Edit Element').should('not.exist') - cy.get('[data-cy="admin-element-title"]') - .should('have.value', 'Baderech HaAvodah') - }) - - it('saves element edits and restores the seed content through the UI', () => { - const originalTitle = 'Baderech HaAvodah' - const originalDescription = 'a structured path for inner and outer growth' - const originalIconImageUrl = '/assets/baderech-haavodah-icon.png' - const updatedTitle = 'Baderech HaAvodah Updated' - const updatedDescription = 'Updated admin description' - const updatedRichText = '

Updated admin rich text

' - - loginAsAdmin() - cy.visit('/admin/element/1') - - fillElementForm( - updatedTitle, - updatedDescription, - originalIconImageUrl, - updatedRichText, - '', - '', - ) - cy.get('[data-cy="admin-element-save"]').click() - - cy.get('[data-cy="admin-element-status"]') - .should('be.visible') - .and('contain.text', 'Saved') - cy.contains('header.site-header a', 'View Element').click() - cy.location('pathname').should('eq', '/element/1') - cy.contains('h1', updatedTitle).should('be.visible') - cy.get('[data-cy="element-rich-text"]') - .should('contain.text', 'Updated admin rich text') - - cy.visit('/admin/element/1') - fillElementForm( - originalTitle, - originalDescription, - originalIconImageUrl, - '', - '', - '', - ) - cy.get('[data-cy="admin-element-save"]').click() - cy.get('[data-cy="admin-element-status"]') - .should('be.visible') - .and('contain.text', 'Saved') - }) -}) diff --git a/frontend/rabbi_gerzi/cypress/e2e/database-reset.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/database-reset.cy.ts deleted file mode 100644 index dd8ec44..0000000 --- a/frontend/rabbi_gerzi/cypress/e2e/database-reset.cy.ts +++ /dev/null @@ -1,41 +0,0 @@ -function loginAsAdmin(): void { - cy.visit('/login') - - cy.get('[data-cy="login-email"]').type('admin@rabbigerzi.test') - cy.get('[data-cy="login-password"]').type('password123!@#') - cy.get('[data-cy="login-submit"]').click() - - cy.url().should('eq', Cypress.config().baseUrl + '/') - cy.getCookie('auth_token').should('exist') -} - -describe('cypress database reset', () => { - it('can dirty element seed data', () => { - loginAsAdmin() - - cy.visit('/admin/element/1') - cy.get('[data-cy="admin-element-title"]') - .clear() - .type('Dirty Cypress Element') - cy.get('[data-cy="admin-element-description"]') - .clear() - .type('Dirty Cypress description') - cy.get('[data-cy="admin-element-rich-text"]') - .clear() - .type('

Dirty Cypress rich text

', { - parseSpecialCharSequences: false, - }) - cy.get('[data-cy="admin-element-save"]').click() - - cy.get('[data-cy="admin-element-status"]') - .should('be.visible') - .and('contain.text', 'Saved') - }) - - it('starts the next test from seeded element data', () => { - cy.visit('/element/1') - - cy.contains('h1', 'Baderech HaAvodah').should('be.visible') - cy.get('[data-cy="element-rich-text"]').should('not.exist') - }) -}) diff --git a/frontend/rabbi_gerzi/cypress/support/commands.ts b/frontend/rabbi_gerzi/cypress/support/commands.ts deleted file mode 100644 index 65696c1..0000000 --- a/frontend/rabbi_gerzi/cypress/support/commands.ts +++ /dev/null @@ -1,16 +0,0 @@ -/// -/* eslint-disable @typescript-eslint/no-namespace */ - -declare global { - namespace Cypress { - interface Chainable { - resetDb(): Chainable - } - } -} - -Cypress.Commands.add('resetDb', function () { - return cy.task('db:reset') -}) - -export {} diff --git a/frontend/rabbi_gerzi/cypress/support/e2e.ts b/frontend/rabbi_gerzi/cypress/support/e2e.ts index c8f94e6..9b15a88 100644 --- a/frontend/rabbi_gerzi/cypress/support/e2e.ts +++ b/frontend/rabbi_gerzi/cypress/support/e2e.ts @@ -1,9 +1,3 @@ -import './commands' - -beforeEach(() => { - cy.resetDb() - cy.clearCookies() - cy.clearLocalStorage() -}) - +// Loaded automatically before every spec file in cypress/e2e/. +// Intentionally minimal - add shared commands here when needed. export {} diff --git a/frontend/rabbi_gerzi/cypress/tsconfig.json b/frontend/rabbi_gerzi/cypress/tsconfig.json deleted file mode 100644 index ddfb30e..0000000 --- a/frontend/rabbi_gerzi/cypress/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.app.json", - "include": ["./**/*.ts"], - "compilerOptions": { - "types": ["cypress", "node"], - "isolatedModules": false, - "noUncheckedIndexedAccess": false, - "noEmit": false, - "allowImportingTsExtensions": false, - "ignoreDeprecations": "6.0" - } -} diff --git a/frontend/rabbi_gerzi/package-lock.json b/frontend/rabbi_gerzi/package-lock.json index cd5beb4..b1578ed 100644 --- a/frontend/rabbi_gerzi/package-lock.json +++ b/frontend/rabbi_gerzi/package-lock.json @@ -16,7 +16,6 @@ "@bahmutov/cypress-esbuild-preprocessor": "^2.2.8", "@tsconfig/node24": "^24.0.4", "@types/node": "^24.12.2", - "@types/pg": "^8.20.0", "@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue-jsx": "^5.1.5", "@vue/eslint-config-typescript": "^14.7.0", @@ -31,7 +30,6 @@ "npm-run-all2": "^8.0.4", "oxfmt": "^0.45.0", "oxlint": "~1.60.0", - "pg": "^8.21.0", "typescript": "~6.0.0", "vite": "^8.0.8", "vite-plugin-vue-devtools": "^8.1.1", @@ -2346,18 +2344,6 @@ "undici-types": "~7.16.0" } }, - "node_modules/@types/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -6509,103 +6495,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pg": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", - "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.13.0", - "pg-pool": "^3.14.0", - "pg-protocol": "^1.14.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.4.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", - "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", - "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", - "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", - "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6722,49 +6611,6 @@ "node": ">=4" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7255,16 +7101,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -8390,16 +8226,6 @@ "node": ">=12" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/rabbi_gerzi/package.json b/frontend/rabbi_gerzi/package.json index 0506b7b..18bec3a 100644 --- a/frontend/rabbi_gerzi/package.json +++ b/frontend/rabbi_gerzi/package.json @@ -25,7 +25,6 @@ "@bahmutov/cypress-esbuild-preprocessor": "^2.2.8", "@tsconfig/node24": "^24.0.4", "@types/node": "^24.12.2", - "@types/pg": "^8.20.0", "@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue-jsx": "^5.1.5", "@vue/eslint-config-typescript": "^14.7.0", @@ -40,7 +39,6 @@ "npm-run-all2": "^8.0.4", "oxfmt": "^0.45.0", "oxlint": "~1.60.0", - "pg": "^8.21.0", "typescript": "~6.0.0", "vite": "^8.0.8", "vite-plugin-vue-devtools": "^8.1.1", diff --git a/frontend/rabbi_gerzi/src/components/SiteHeader.vue b/frontend/rabbi_gerzi/src/components/SiteHeader.vue index 0ecdc90..65497a5 100644 --- a/frontend/rabbi_gerzi/src/components/SiteHeader.vue +++ b/frontend/rabbi_gerzi/src/components/SiteHeader.vue @@ -1,15 +1,9 @@