feat: ajout de la fonctionnalité de récupération des chapitres de manga, avec mise à jour de l'API et des composants pour gérer la récupération asynchrone des chapitres, ainsi que des améliorations dans la gestion des erreurs et des tests associés.
This commit is contained in:
parent
5a5569cf2c
commit
ee2a9b3750
@@ -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 ---
|
// --- Scrape Chapter Action ---
|
||||||
async searchChapter(chapterId) {
|
async searchChapter(chapterId) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -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) {
|
async searchChapter(chapterId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/scraping/chapters', {
|
const response = await fetch('/api/scraping/chapters', {
|
||||||
|
|||||||
@@ -147,6 +147,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await mangaStore.createFromMangaDex(selectedManga.value.externalId);
|
await mangaStore.createFromMangaDex(selectedManga.value.externalId);
|
||||||
|
await mangaStore.fetchMangaChapters(selectedManga.value.id);
|
||||||
router.push('/manga');
|
router.push('/manga');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Erreur d'ajout:", e);
|
console.error("Erreur d'ajout:", e);
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ namespace App\Domain\Manga\Application\Command;
|
|||||||
readonly class FetchMangaChapters
|
readonly class FetchMangaChapters
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $externalId
|
public string $mangaId
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,6 @@ readonly class CreateMangaFromMangadexHandler
|
|||||||
|
|
||||||
$this->mangaRepository->save($manga);
|
$this->mangaRepository->save($manga);
|
||||||
|
|
||||||
$this->messageBus->dispatch(new MangaCreated($command->externalId));
|
$this->messageBus->dispatch(new MangaCreated($manga->getId()->getValue(), $command->externalId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ readonly class CreateMangaHandler
|
|||||||
$this->mangaRepository->save($manga);
|
$this->mangaRepository->save($manga);
|
||||||
|
|
||||||
if ($command->externalId) {
|
if ($command->externalId) {
|
||||||
$this->messageBus->dispatch(new MangaCreated($command->externalId));
|
$this->messageBus->dispatch(new MangaCreated($manga->getId()->getValue(), $command->externalId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,18 @@ readonly class FetchMangaChaptersHandler
|
|||||||
|
|
||||||
public function handle(FetchMangaChapters $command): void
|
public function handle(FetchMangaChapters $command): void
|
||||||
{
|
{
|
||||||
$manga = $this->mangaRepository->findByExternalId(new ExternalId($command->externalId));
|
$manga = $this->mangaRepository->findById($command->mangaId);
|
||||||
|
|
||||||
if ($manga === null) {
|
if ($manga === null) {
|
||||||
throw new \RuntimeException('Manga not found');
|
throw new \RuntimeException('Manga not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($manga->getExternalId() === null) {
|
||||||
|
throw new \RuntimeException('Manga has no external ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
$externalId = $manga->getExternalId()->getValue();
|
||||||
|
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
$limit = 500;
|
$limit = 500;
|
||||||
$hasMore = true;
|
$hasMore = true;
|
||||||
@@ -34,7 +40,7 @@ readonly class FetchMangaChaptersHandler
|
|||||||
|
|
||||||
while ($hasMore) {
|
while ($hasMore) {
|
||||||
$feed = $this->mangadexClient->getMangaFeed(
|
$feed = $this->mangadexClient->getMangaFeed(
|
||||||
$command->externalId,
|
$externalId,
|
||||||
$offset,
|
$offset,
|
||||||
$limit
|
$limit
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Domain\Manga\Domain\Event;
|
|||||||
readonly class MangaCreated
|
readonly class MangaCreated
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
public string $mangaId,
|
||||||
public string $externalId
|
public string $externalId
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaProce
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
shortName: 'Manga',
|
shortName: 'Mangadex',
|
||||||
operations: [
|
operations: [
|
||||||
new Post(
|
new Post(
|
||||||
uriTemplate: '/mangas/create-from-mangadex',
|
uriTemplate: '/mangas/create-from-mangadex',
|
||||||
|
|||||||
@@ -8,19 +8,53 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\FetchMangaChapte
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
shortName: 'Chapters',
|
shortName: 'Mangadex',
|
||||||
operations: [
|
operations: [
|
||||||
new Post(
|
new Post(
|
||||||
uriTemplate: '/manga/chapters/fetch',
|
uriTemplate: '/manga/chapters/fetch',
|
||||||
processor: FetchMangaChaptersProcessor::class,
|
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
|
class FetchMangaChaptersResource
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank(message: 'L\'identifiant du manga est obligatoire')]
|
||||||
public string $externalId
|
#[Assert\Uuid(message: 'L\'identifiant du manga doit être un UUID valide')]
|
||||||
|
public string $mangaId
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ readonly class FetchMangaChaptersProcessor implements ProcessorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->messageBus->dispatch(
|
$this->messageBus->dispatch(
|
||||||
new FetchMangaChapters($data->externalId)
|
new FetchMangaChapters($data->mangaId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ readonly class MangaCreatedListener
|
|||||||
public function __invoke(MangaCreated $event): void
|
public function __invoke(MangaCreated $event): void
|
||||||
{
|
{
|
||||||
$this->messageBus->dispatch(
|
$this->messageBus->dispatch(
|
||||||
new FetchMangaChapters($event->externalId)
|
new FetchMangaChapters($event->mangaId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,9 +31,10 @@ class FetchMangaChaptersHandlerTest extends TestCase
|
|||||||
|
|
||||||
public function testHandleWithExistingManga(): void
|
public function testHandleWithExistingManga(): void
|
||||||
{
|
{
|
||||||
|
$mangaId = 'manga-id';
|
||||||
$externalId = 'manga-123';
|
$externalId = 'manga-123';
|
||||||
$manga = new Manga(
|
$manga = new Manga(
|
||||||
new MangaId('manga-id'),
|
new MangaId($mangaId),
|
||||||
new MangaTitle('Test Manga'),
|
new MangaTitle('Test Manga'),
|
||||||
new MangaSlug('test-manga'),
|
new MangaSlug('test-manga'),
|
||||||
'Description',
|
'Description',
|
||||||
@@ -58,7 +59,7 @@ class FetchMangaChaptersHandlerTest extends TestCase
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$command = new FetchMangaChapters($externalId);
|
$command = new FetchMangaChapters($mangaId);
|
||||||
$this->handler->handle($command);
|
$this->handler->handle($command);
|
||||||
|
|
||||||
$this->assertCount(1, $this->mangaRepository->getSavedChapters());
|
$this->assertCount(1, $this->mangaRepository->getSavedChapters());
|
||||||
@@ -66,12 +67,36 @@ class FetchMangaChaptersHandlerTest extends TestCase
|
|||||||
|
|
||||||
public function testHandleWithNonExistingManga(): void
|
public function testHandleWithNonExistingManga(): void
|
||||||
{
|
{
|
||||||
$externalId = 'non-existing-manga';
|
$mangaId = 'non-existing-manga';
|
||||||
|
|
||||||
$this->expectException(\RuntimeException::class);
|
$this->expectException(\RuntimeException::class);
|
||||||
$this->expectExceptionMessage('Manga not found');
|
$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);
|
$this->handler->handle($command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
|||||||
|
|
||||||
public function testFetchChaptersForExistingManga(): void
|
public function testFetchChaptersForExistingManga(): void
|
||||||
{
|
{
|
||||||
|
$mangaId = 'manga-id';
|
||||||
$externalId = 'manga-123';
|
$externalId = 'manga-123';
|
||||||
$manga = new Manga(
|
$manga = new Manga(
|
||||||
new MangaId('manga-id'),
|
new MangaId($mangaId),
|
||||||
new MangaTitle('Test Manga'),
|
new MangaTitle('Test Manga'),
|
||||||
new MangaSlug('test-manga'),
|
new MangaSlug('test-manga'),
|
||||||
'Description',
|
'Description',
|
||||||
@@ -48,7 +49,7 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
|||||||
|
|
||||||
static::createClient()->request('POST', '/api/manga/chapters/fetch', [
|
static::createClient()->request('POST', '/api/manga/chapters/fetch', [
|
||||||
'json' => [
|
'json' => [
|
||||||
'externalId' => $externalId
|
'mangaId' => $mangaId
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -57,14 +58,14 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
|||||||
$messages = $this->messageBus->getDispatchedMessages();
|
$messages = $this->messageBus->getDispatchedMessages();
|
||||||
$this->assertCount(1, $messages);
|
$this->assertCount(1, $messages);
|
||||||
$this->assertInstanceOf(FetchMangaChapters::class, $messages[0]);
|
$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', [
|
$response = static::createClient()->request('POST', '/api/manga/chapters/fetch', [
|
||||||
'json' => [
|
'json' => [
|
||||||
'externalId' => ''
|
'mangaId' => ''
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -72,8 +73,8 @@ class FetchMangaChaptersTest extends AbstractApiTestCase
|
|||||||
$this->assertJsonContains([
|
$this->assertJsonContains([
|
||||||
'violations' => [
|
'violations' => [
|
||||||
[
|
[
|
||||||
'propertyPath' => 'externalId',
|
'propertyPath' => 'mangaId',
|
||||||
'message' => 'This value should not be blank.'
|
'message' => 'L\'identifiant du manga est obligatoire'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|||||||
Reference in New Issue
Block a user