diff --git a/backend/app/Controllers/ElementController.php b/backend/app/Controllers/ElementController.php
index 31c162c..5fc14d7 100644
--- a/backend/app/Controllers/ElementController.php
+++ b/backend/app/Controllers/ElementController.php
@@ -339,8 +339,9 @@ class ElementController
'id' => $element->getId(),
'title' => $element->getTitle(),
'description' => $element->getDescription(),
- 'iconImageUrl' => $this->iconImageUrl(
- $element->getIconImageUrl()
+ 'iconImageUrl' => $this->storageUrl(
+ $element->getIconImageUrl(),
+ 'element-icons/',
),
'richText' => $element->getRichText(),
'shortPdfPath' => $this->storageUrl(
@@ -396,22 +397,6 @@ class ElementController
];
}
- private function iconImageUrl(?string $path): ?string
- {
- if ($path === null) {
- return null;
- }
-
- if (
- str_starts_with($path, 'element-icons/')
- || str_starts_with($path, 'set-icons/')
- ) {
- return $this->filesystem->url($path);
- }
-
- return $path;
- }
-
private function storageUrl(?string $path, string $folderPrefix): ?string
{
if ($path === null) {
diff --git a/backend/app/Controllers/SetController.php b/backend/app/Controllers/SetController.php
index 37b846f..6fcbb8c 100644
--- a/backend/app/Controllers/SetController.php
+++ b/backend/app/Controllers/SetController.php
@@ -3,23 +3,15 @@
namespace App\Controllers;
use App\Element\ElementRepository;
-use App\Exceptions\BadRequestException;
use App\Set\Set as DomainSet;
use App\Set\SetRepository;
-use App\Set\UseCases\CreateSetWithRoot\CreateSetWithRoot;
-use App\Set\UseCases\CreateSetWithRoot\CreateSetWithRootRequest;
-use App\Shared\Files\Filesystem;
use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
-use Illuminate\Http\UploadedFile;
class SetController
{
public function __construct(
private SetRepository $setRepository,
private ElementRepository $elementRepository,
- private CreateSetWithRoot $createSetWithRoot,
- private Filesystem $filesystem,
) {
}
@@ -35,32 +27,6 @@ class SetController
], 200);
}
- public function create(Request $request): JsonResponse
- {
- $iconImage = $this->uploadedFileInput($request, 'iconImage');
-
- try {
- $set = $this->createSetWithRoot->execute(
- new CreateSetWithRootRequest(
- 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);
- }
-
- return new JsonResponse([
- 'set' => $this->buildSetPayload($set),
- ], 201);
- }
-
/**
* @return array{
* id: int,
@@ -78,77 +44,8 @@ class SetController
'id' => $set->getId(),
'name' => $set->getName(),
'description' => $set->getDescription(),
- 'iconImageUrl' => $this->storageUrl(
- $set->getIconImageUrl(),
- 'set-icons/',
- ),
+ 'iconImageUrl' => $set->getIconImageUrl(),
'rootElementId' => $rootElement?->getId(),
];
}
-
- private function stringInput(Request $request, string $key): ?string
- {
- if (! $request->exists($key)) {
- return null;
- }
-
- $value = $request->input($key);
- if ($value === null) {
- return '';
- }
-
- if (! is_string($value)) {
- return null;
- }
-
- return $value;
- }
-
- /**
- * @return array{
- * contents: string|null,
- * originalName: string|null,
- * mimeType: string|null,
- * sizeBytes: int|null
- * }
- */
- private function uploadedFileInput(Request $request, string $key): array
- {
- $emptyFileInput = [
- 'contents' => null,
- 'originalName' => null,
- 'mimeType' => null,
- 'sizeBytes' => null,
- ];
-
- if (! $request->hasFile($key)) {
- return $emptyFileInput;
- }
-
- $file = $request->file($key);
- if (! $file instanceof UploadedFile) {
- return $emptyFileInput;
- }
-
- $realPath = $file->getRealPath();
- if ($realPath === false) {
- return $emptyFileInput;
- }
-
- return [
- 'contents' => (string) file_get_contents($realPath),
- 'originalName' => $file->getClientOriginalName(),
- 'mimeType' => (string) $file->getMimeType(),
- 'sizeBytes' => (int) $file->getSize(),
- ];
- }
-
- private function storageUrl(string $path, string $folderPrefix): string
- {
- if (str_starts_with($path, $folderPrefix)) {
- return $this->filesystem->url($path);
- }
-
- return $path;
- }
}
diff --git a/backend/app/Set/UseCases/CreateSetWithRoot/CreateSetWithRoot.php b/backend/app/Set/UseCases/CreateSetWithRoot/CreateSetWithRoot.php
deleted file mode 100644
index 9d9c98d..0000000
--- a/backend/app/Set/UseCases/CreateSetWithRoot/CreateSetWithRoot.php
+++ /dev/null
@@ -1,106 +0,0 @@
-name === null || $request->name === '') {
- throw new BadRequestException('name is required');
- }
- if ($request->description === null || $request->description === '') {
- throw new BadRequestException('description is required');
- }
-
- $iconPath = $this->uploadIconImage($request);
- $set = $this->setRepository->create(new CreateSetDto(
- name: $request->name,
- description: $request->description,
- iconImageUrl: $iconPath,
- ));
-
- $this->elementRepository->create(new CreateElementDto(
- set: $set,
- title: $set->getName(),
- description: $set->getDescription(),
- iconImageUrl: $iconPath,
- richText: '',
- shortPdfPath: null,
- longPdfPath: null,
- youtubeUrl: null,
- parentElement: null,
- ));
-
- return $set;
- }
-
- /**
- * @throws BadRequestException
- */
- private function uploadIconImage(CreateSetWithRootRequest $request): string
- {
- if (
- $request->iconImageContents === null
- || $request->iconImageOriginalName === null
- || $request->iconImageMimeType === null
- || $request->iconImageSizeBytes === null
- ) {
- throw new BadRequestException('icon image is required');
- }
-
- if (
- ! in_array(
- $request->iconImageMimeType,
- self::ALLOWED_MIME_TYPES,
- true,
- )
- ) {
- throw new BadRequestException(
- 'icon image must be a jpeg, png or webp image',
- );
- }
-
- if ($request->iconImageSizeBytes > self::MAX_SIZE_BYTES) {
- throw new BadRequestException(
- 'icon image must be 5MB or smaller',
- );
- }
-
- $iconImage = new FileToUpload(
- contents: $request->iconImageContents,
- originalName: $request->iconImageOriginalName,
- mimeType: $request->iconImageMimeType,
- sizeBytes: $request->iconImageSizeBytes,
- );
-
- return $this->filesystem->upload($iconImage, 'set-icons');
- }
-}
diff --git a/backend/app/Set/UseCases/CreateSetWithRoot/CreateSetWithRootRequest.php b/backend/app/Set/UseCases/CreateSetWithRoot/CreateSetWithRootRequest.php
deleted file mode 100644
index 166a901..0000000
--- a/backend/app/Set/UseCases/CreateSetWithRoot/CreateSetWithRootRequest.php
+++ /dev/null
@@ -1,16 +0,0 @@
-middleware(AuthMiddleware::class);
Route::get('/sets', [SetController::class, 'index']);
-Route::post('/sets', [SetController::class, 'create'])
- ->middleware(AuthMiddleware::class);
Route::get('/elements/{id}', [ElementController::class, 'show']);
Route::get('/element-pdfs/{folder}/{fileName}', [
ElementController::class,
diff --git a/backend/tests/Feature/SetsEndpointTest.php b/backend/tests/Feature/SetsEndpointTest.php
index 8f4af23..2f8264b 100644
--- a/backend/tests/Feature/SetsEndpointTest.php
+++ b/backend/tests/Feature/SetsEndpointTest.php
@@ -2,20 +2,11 @@
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 Illuminate\Http\UploadedFile;
-use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class SetsEndpointTest extends TestCase
@@ -72,124 +63,4 @@ class SetsEndpointTest extends TestCase
],
]);
}
-
- public function testCreateSetRequiresAuthentication(): void
- {
- $response = $this->postJson('/api/sets', [
- 'name' => 'New Series',
- 'description' => 'A new learning series',
- ]);
-
- $response->assertUnauthorized();
- $response->assertExactJson([
- 'error' => 'unauthenticated',
- ]);
- }
-
- public function testAuthenticatedCreateSetCreatesRootElement(): void
- {
- Storage::fake('public');
- $setRepository = app(SetRepository::class);
- $elementRepository = app(ElementRepository::class);
- $this->createSession('valid-token');
-
- $response = $this->withCredentials()
- ->withUnencryptedCookie('auth_token', 'valid-token')
- ->post('/api/sets', [
- 'name' => 'New Series',
- 'description' => 'A new learning series',
- 'iconImage' => $this->iconImageUpload(),
- ]);
-
- $response->assertCreated();
- $body = $response->json();
- $setId = $body['set']['id'];
- $rootElementId = $body['set']['rootElementId'];
- $this->assertIsInt($setId);
- $this->assertIsInt($rootElementId);
- $this->assertSame('New Series', $body['set']['name']);
- $this->assertSame(
- 'A new learning series',
- $body['set']['description'],
- );
- $this->assertStringContainsString(
- '/storage/set-icons/',
- $body['set']['iconImageUrl'],
- );
-
- $createdSet = $setRepository->find($setId);
- $this->assertNotNull($createdSet);
- $rootElement = $elementRepository->findRootBySet($createdSet);
- $this->assertNotNull($rootElement);
- $this->assertSame($rootElementId, $rootElement->getId());
- $this->assertSame('New Series', $rootElement->getTitle());
- $this->assertSame(
- 'A new learning series',
- $rootElement->getDescription(),
- );
- $storedIconPath = $createdSet->getIconImageUrl();
- $this->assertStringStartsWith('set-icons/', $storedIconPath);
- $this->assertSame($storedIconPath, $rootElement->getIconImageUrl());
- Storage::disk('public')->assertExists($storedIconPath);
-
- $elementResponse = $this->getJson("/api/elements/$rootElementId");
- $elementResponse->assertOk();
- $elementResponse->assertJsonPath(
- 'element.iconImageUrl',
- $body['set']['iconImageUrl'],
- );
- }
-
- public function testCreateSetRequiresIconImage(): void
- {
- $this->createSession('valid-token');
-
- $response = $this->withCredentials()
- ->withUnencryptedCookie('auth_token', 'valid-token')
- ->post('/api/sets', [
- 'name' => 'New Series',
- 'description' => 'A new learning series',
- ]);
-
- $response->assertBadRequest();
- $response->assertExactJson([
- 'error' => 'icon image is required',
- ]);
- }
-
- 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'),
- ),
- ));
- }
-
- private function iconImageUpload(): UploadedFile
- {
- $fixturePath = __DIR__ . '/../fixtures/icon.png';
-
- return new UploadedFile(
- $fixturePath,
- 'icon.png',
- 'image/png',
- null,
- true,
- );
- }
}
diff --git a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts
index 9aca794..0233789 100644
--- a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts
+++ b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts
@@ -1,14 +1,3 @@
-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('media page sets', () => {
it('fetches and renders seeded set cards', () => {
const introductionAudioTitle =
@@ -174,35 +163,4 @@ describe('media page sets', () => {
.and('include', 'https://www.youtube.com/embed/yHx-r4p6hHU')
.and('include', 'start=1')
})
-
- it('does not show the create set card to logged-out users', () => {
- cy.visit('/media')
-
- cy.get('[data-cy="media-create-set-card"]').should('not.exist')
- })
-
- it('creates a set from the logged-in media page modal', () => {
- loginAsAdmin()
- cy.visit('/media')
- cy.intercept('POST', /\/api\/sets$/).as('createSet')
-
- cy.get('[data-cy="media-create-set-card"]')
- .should('be.visible')
- .and('contain.text', 'Create another set')
- .click()
- cy.get('[data-cy="create-set-modal"]').should('be.visible')
- cy.get('[data-cy="create-set-name"]').type('New Cypress Set')
- cy.get('[data-cy="create-set-description"]')
- .type('Created through the media page modal')
- cy.get('[data-cy="create-set-icon"]')
- .selectFile('public/assets/baderech-haavodah-icon.png')
- cy.get('[data-cy="create-set-submit"]').click()
- cy.wait('@createSet')
-
- cy.location('pathname').should('match', /^\/admin\/element\/\d+$/)
- cy.get('[data-cy="admin-element-title"]')
- .should('have.value', 'New Cypress Set')
- cy.get('[data-cy="admin-element-description"]')
- .should('have.value', 'Created through the media page modal')
- })
})
diff --git a/frontend/rabbi_gerzi/src/stores/mediaSets.ts b/frontend/rabbi_gerzi/src/stores/mediaSets.ts
index e869470..b899929 100644
--- a/frontend/rabbi_gerzi/src/stores/mediaSets.ts
+++ b/frontend/rabbi_gerzi/src/stores/mediaSets.ts
@@ -9,32 +9,16 @@ export interface MediaSet {
rootElementId: number | null
}
-export interface CreateMediaSetInput {
- name: string
- description: string
- iconImage: File
-}
-
interface SetsResponse {
sets: MediaSet[]
}
-interface CreateSetResponse {
- set: MediaSet
-}
-
-interface ErrorResponse {
- error?: string
-}
-
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
export const useMediaSetsStore = defineStore('mediaSets', () => {
const sets = ref
+
No media sets are available yet.