diff --git a/assets/vue/app/domain/manga/application/store/mangaStore.js b/assets/vue/app/domain/manga/application/store/mangaStore.js index 81450c5..2cb9c19 100644 --- a/assets/vue/app/domain/manga/application/store/mangaStore.js +++ b/assets/vue/app/domain/manga/application/store/mangaStore.js @@ -189,6 +189,22 @@ export const useMangaStore = defineStore('manga', { } }, + async fetchMangaChapters(mangaId) { + if (this.loadingChapters) return; + this.loadingChapters = true; + this.chaptersError = null; + + try { + await mangaRepository.fetchMangaChapters(mangaId); + this.mangaChapters[mangaId] = chaptersData; + console.log('Chapitres récupérés avec succès'); + } catch (err) { + this.chaptersError = err.message; + } finally { + this.loadingChapters = false; + } + }, + // --- Scrape Chapter Action --- async searchChapter(chapterId) { try { diff --git a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js index 11c2ebd..dfe22d1 100644 --- a/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js +++ b/assets/vue/app/domain/manga/infrastructure/api/apiMangaRepository.js @@ -123,6 +123,25 @@ export class ApiMangaRepository { } } + async fetchMangaChapters(mangaId) { + try { + const response = await fetch(`/api/manga/chapters/fetch`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mangaId }) + }); + if (!response.ok) { + throw new Error('Failed to fetch manga chapters'); + } + return await response.json(); + } catch (error) { + console.error('API Error:', error); + throw error; + } + } + async searchChapter(chapterId) { try { const response = await fetch('/api/scraping/chapters', { diff --git a/assets/vue/app/domain/manga/presentation/pages/AddManga.vue b/assets/vue/app/domain/manga/presentation/pages/AddManga.vue index 46e41c5..d8658eb 100644 --- a/assets/vue/app/domain/manga/presentation/pages/AddManga.vue +++ b/assets/vue/app/domain/manga/presentation/pages/AddManga.vue @@ -147,6 +147,7 @@ try { await mangaStore.createFromMangaDex(selectedManga.value.externalId); + await mangaStore.fetchMangaChapters(selectedManga.value.id); router.push('/manga'); } catch (e) { console.error("Erreur d'ajout:", e); diff --git a/src/Domain/Manga/Application/Command/FetchMangaChapters.php b/src/Domain/Manga/Application/Command/FetchMangaChapters.php index d76a952..142919e 100644 --- a/src/Domain/Manga/Application/Command/FetchMangaChapters.php +++ b/src/Domain/Manga/Application/Command/FetchMangaChapters.php @@ -5,6 +5,6 @@ namespace App\Domain\Manga\Application\Command; readonly class FetchMangaChapters { public function __construct( - public string $externalId + public string $mangaId ) {} -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php b/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php index 95373ab..a25c27f 100644 --- a/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php @@ -24,7 +24,7 @@ readonly class CreateMangaFromMangadexHandler public function handle(CreateMangaFromMangadex $command): void { $manga = $this->mangaProvider->findByExternalId(new ExternalId($command->externalId)); - + if ($manga === null) { throw new MangaNotFoundException('Manga not found on Mangadex'); } @@ -32,10 +32,10 @@ readonly class CreateMangaFromMangadexHandler try { // Télécharge l'image originale $fullImagePath = $this->imageProcessor->downloadImage($manga->getImageUrl()); - + // Crée la miniature à partir de l'image originale $thumbnailPath = $this->imageProcessor->createThumbnail($fullImagePath); - + // Met à jour le manga avec les nouveaux chemins d'images $manga->updateImageUrls(new ImageUrls($fullImagePath, $thumbnailPath)); } catch (\Exception $e) { @@ -43,7 +43,7 @@ readonly class CreateMangaFromMangadexHandler } $this->mangaRepository->save($manga); - - $this->messageBus->dispatch(new MangaCreated($command->externalId)); + + $this->messageBus->dispatch(new MangaCreated($manga->getId()->getValue(), $command->externalId)); } -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php b/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php index 2061787..d83cf65 100644 --- a/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php @@ -54,7 +54,7 @@ readonly class CreateMangaHandler $this->mangaRepository->save($manga); if ($command->externalId) { - $this->messageBus->dispatch(new MangaCreated($command->externalId)); + $this->messageBus->dispatch(new MangaCreated($manga->getId()->getValue(), $command->externalId)); } } } diff --git a/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php b/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php index 9f9c353..deb8b16 100644 --- a/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php @@ -19,12 +19,18 @@ readonly class FetchMangaChaptersHandler public function handle(FetchMangaChapters $command): void { - $manga = $this->mangaRepository->findByExternalId(new ExternalId($command->externalId)); + $manga = $this->mangaRepository->findById($command->mangaId); if ($manga === null) { throw new \RuntimeException('Manga not found'); } + if ($manga->getExternalId() === null) { + throw new \RuntimeException('Manga has no external ID'); + } + + $externalId = $manga->getExternalId()->getValue(); + $offset = 0; $limit = 500; $hasMore = true; @@ -34,7 +40,7 @@ readonly class FetchMangaChaptersHandler while ($hasMore) { $feed = $this->mangadexClient->getMangaFeed( - $command->externalId, + $externalId, $offset, $limit ); diff --git a/src/Domain/Manga/Domain/Event/MangaCreated.php b/src/Domain/Manga/Domain/Event/MangaCreated.php index 4c31c5f..449f7dc 100644 --- a/src/Domain/Manga/Domain/Event/MangaCreated.php +++ b/src/Domain/Manga/Domain/Event/MangaCreated.php @@ -5,6 +5,7 @@ namespace App\Domain\Manga\Domain\Event; readonly class MangaCreated { public function __construct( + public string $mangaId, public string $externalId ) {} -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php index 0d8eb26..d10d629 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php @@ -8,7 +8,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaProce use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( - shortName: 'Manga', + shortName: 'Mangadex', operations: [ new Post( uriTemplate: '/mangas/create-from-mangadex', @@ -40,4 +40,4 @@ class CreateMangaResource { #[Assert\NotBlank] public string $externalId; -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php index 6885cee..551c4ed 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php @@ -8,19 +8,53 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\FetchMangaChapte use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( - shortName: 'Chapters', + shortName: 'Mangadex', operations: [ new Post( uriTemplate: '/manga/chapters/fetch', processor: FetchMangaChaptersProcessor::class, - status: 202 + status: 202, + description: 'Déclenche la récupération des chapitres d\'un manga', + openapiContext: [ + 'summary' => 'Récupérer les chapitres d\'un manga', + 'description' => 'Lance le processus de récupération des chapitres depuis la source externe pour un manga donné', + 'requestBody' => [ + 'description' => 'Données requises pour récupérer les chapitres', + 'required' => true, + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'mangaId' => [ + 'type' => 'string', + 'format' => 'uuid', + 'description' => 'L\'identifiant unique du manga', + 'example' => '123e4567-e89b-12d3-a456-426614174000' + ] + ], + 'required' => ['mangaId'] + ] + ] + ] + ], + 'responses' => [ + '202' => [ + 'description' => 'Demande de récupération acceptée et mise en file d\'attente' + ], + '422' => [ + 'description' => 'Données de validation invalides' + ] + ] + ] ) ] )] class FetchMangaChaptersResource { public function __construct( - #[Assert\NotBlank] - public string $externalId + #[Assert\NotBlank(message: 'L\'identifiant du manga est obligatoire')] + #[Assert\Uuid(message: 'L\'identifiant du manga doit être un UUID valide')] + public string $mangaId ) {} -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php index 89abd2e..84937eb 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php @@ -21,7 +21,7 @@ readonly class FetchMangaChaptersProcessor implements ProcessorInterface } $this->messageBus->dispatch( - new FetchMangaChapters($data->externalId) + new FetchMangaChapters($data->mangaId) ); } -} \ No newline at end of file +} diff --git a/src/Domain/Manga/Infrastructure/EventListener/MangaCreatedListener.php b/src/Domain/Manga/Infrastructure/EventListener/MangaCreatedListener.php index 76be2c1..732dcd6 100644 --- a/src/Domain/Manga/Infrastructure/EventListener/MangaCreatedListener.php +++ b/src/Domain/Manga/Infrastructure/EventListener/MangaCreatedListener.php @@ -15,7 +15,7 @@ readonly class MangaCreatedListener public function __invoke(MangaCreated $event): void { $this->messageBus->dispatch( - new FetchMangaChapters($event->externalId) + new FetchMangaChapters($event->mangaId) ); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php index cb8576b..997886d 100644 --- a/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandlerTest.php @@ -31,9 +31,10 @@ class FetchMangaChaptersHandlerTest extends TestCase public function testHandleWithExistingManga(): void { + $mangaId = 'manga-id'; $externalId = 'manga-123'; $manga = new Manga( - new MangaId('manga-id'), + new MangaId($mangaId), new MangaTitle('Test Manga'), new MangaSlug('test-manga'), 'Description', @@ -58,7 +59,7 @@ class FetchMangaChaptersHandlerTest extends TestCase ] ]); - $command = new FetchMangaChapters($externalId); + $command = new FetchMangaChapters($mangaId); $this->handler->handle($command); $this->assertCount(1, $this->mangaRepository->getSavedChapters()); @@ -66,12 +67,36 @@ class FetchMangaChaptersHandlerTest extends TestCase public function testHandleWithNonExistingManga(): void { - $externalId = 'non-existing-manga'; + $mangaId = 'non-existing-manga'; $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Manga not found'); - $command = new FetchMangaChapters($externalId); + $command = new FetchMangaChapters($mangaId); + $this->handler->handle($command); + } + + public function testHandleWithMangaWithoutExternalId(): void + { + $mangaId = 'manga-id'; + $manga = new Manga( + new MangaId($mangaId), + new MangaTitle('Test Manga'), + new MangaSlug('test-manga'), + 'Description', + 'Author', + 2024, + [], + 'ongoing', + null // Pas d'externalId + ); + + $this->mangaRepository->save($manga); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Manga has no external ID'); + + $command = new FetchMangaChapters($mangaId); $this->handler->handle($command); } } diff --git a/tests/Feature/Manga/FetchMangaChaptersTest.php b/tests/Feature/Manga/FetchMangaChaptersTest.php index 9758ee1..6058ebf 100644 --- a/tests/Feature/Manga/FetchMangaChaptersTest.php +++ b/tests/Feature/Manga/FetchMangaChaptersTest.php @@ -30,9 +30,10 @@ class FetchMangaChaptersTest extends AbstractApiTestCase public function testFetchChaptersForExistingManga(): void { + $mangaId = 'manga-id'; $externalId = 'manga-123'; $manga = new Manga( - new MangaId('manga-id'), + new MangaId($mangaId), new MangaTitle('Test Manga'), new MangaSlug('test-manga'), 'Description', @@ -48,7 +49,7 @@ class FetchMangaChaptersTest extends AbstractApiTestCase static::createClient()->request('POST', '/api/manga/chapters/fetch', [ 'json' => [ - 'externalId' => $externalId + 'mangaId' => $mangaId ] ]); @@ -57,14 +58,14 @@ class FetchMangaChaptersTest extends AbstractApiTestCase $messages = $this->messageBus->getDispatchedMessages(); $this->assertCount(1, $messages); $this->assertInstanceOf(FetchMangaChapters::class, $messages[0]); - $this->assertEquals($externalId, $messages[0]->externalId); + $this->assertEquals($mangaId, $messages[0]->mangaId); } - public function testFetchChaptersWithInvalidExternalId(): void + public function testFetchChaptersWithInvalidMangaId(): void { $response = static::createClient()->request('POST', '/api/manga/chapters/fetch', [ 'json' => [ - 'externalId' => '' + 'mangaId' => '' ] ]); @@ -72,8 +73,8 @@ class FetchMangaChaptersTest extends AbstractApiTestCase $this->assertJsonContains([ 'violations' => [ [ - 'propertyPath' => 'externalId', - 'message' => 'This value should not be blank.' + 'propertyPath' => 'mangaId', + 'message' => 'L\'identifiant du manga est obligatoire' ] ] ]);