diff --git a/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php b/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php new file mode 100644 index 0000000..50c5962 --- /dev/null +++ b/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php @@ -0,0 +1,10 @@ +mangaProvider->findByExternalId(new ExternalId($command->externalId)); + + if ($manga === null) { + throw new MangaNotFoundException('Manga not found on Mangadex'); + } + + $this->mangaRepository->save($manga); + } +} \ No newline at end of file diff --git a/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php b/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php index e5db0be..328dfe9 100644 --- a/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php +++ b/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php @@ -72,4 +72,25 @@ interface MangadexClientInterface * } */ public function getMangaAggregate(string $mangaId): array; + + /** + * @return array{ + * result: string, + * data: array{ + * id: string, + * attributes: array{ + * title: array, + * description: array, + * year: ?int, + * status: string, + * tags: array}}> + * }, + * relationships: array + * } + * } + */ + public function getManga(string $mangaId): array; } \ No newline at end of file diff --git a/src/Domain/Manga/Domain/Contract/Provider/MangaProviderInterface.php b/src/Domain/Manga/Domain/Contract/Provider/MangaProviderInterface.php index 655e414..4565309 100644 --- a/src/Domain/Manga/Domain/Contract/Provider/MangaProviderInterface.php +++ b/src/Domain/Manga/Domain/Contract/Provider/MangaProviderInterface.php @@ -2,9 +2,13 @@ namespace App\Domain\Manga\Domain\Contract\Provider; +use App\Domain\Manga\Domain\Model\Manga; use App\Domain\Manga\Domain\Model\MangaCollection; +use App\Domain\Manga\Domain\Model\ValueObject\ExternalId; interface MangaProviderInterface { public function search(string $title): MangaCollection; + + public function findByExternalId(ExternalId $externalId): ?Manga; } \ No newline at end of file diff --git a/src/Domain/Manga/Domain/Exception/MangaNotFoundException.php b/src/Domain/Manga/Domain/Exception/MangaNotFoundException.php index 3c4921a..51e7660 100644 --- a/src/Domain/Manga/Domain/Exception/MangaNotFoundException.php +++ b/src/Domain/Manga/Domain/Exception/MangaNotFoundException.php @@ -6,8 +6,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class MangaNotFoundException extends NotFoundHttpException { - public function __construct() + public function __construct(string $message = 'Manga not found') { - parent::__construct('Manga not found'); + parent::__construct($message); } } diff --git a/src/Domain/Manga/Domain/Model/Manga.php b/src/Domain/Manga/Domain/Model/Manga.php index b03ae78..1285dba 100644 --- a/src/Domain/Manga/Domain/Model/Manga.php +++ b/src/Domain/Manga/Domain/Model/Manga.php @@ -82,4 +82,9 @@ final class Manga { $this->rating = $rating; } + + public function updateId(MangaId $id): void + { + $this->id = $id; + } } \ 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 new file mode 100644 index 0000000..94c42a5 --- /dev/null +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php @@ -0,0 +1,27 @@ + 'Create a new manga from Mangadex', + 'description' => 'Creates a new manga by fetching its data from Mangadex using an external ID' + ] + ) + ] +)] +class CreateMangaResource +{ + #[Assert\NotBlank] + public string $externalId; +} \ No newline at end of file diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php new file mode 100644 index 0000000..dd80355 --- /dev/null +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php @@ -0,0 +1,26 @@ +handler->handle(new CreateMangaFromMangadex($data->externalId)); + } catch (MangaNotFoundException $e) { + throw new NotFoundHttpException($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/src/Domain/Manga/Infrastructure/Client/MangadexClient.php b/src/Domain/Manga/Infrastructure/Client/MangadexClient.php index 0fab522..7612870 100644 --- a/src/Domain/Manga/Infrastructure/Client/MangadexClient.php +++ b/src/Domain/Manga/Infrastructure/Client/MangadexClient.php @@ -118,6 +118,13 @@ class MangadexClient implements MangadexClientInterface return $this->get('/manga/' . $mangaId . '/aggregate'); } + public function getManga(string $mangaId): array + { + return $this->get('/manga/' . $mangaId, [ + 'includes' => ['cover_art', 'author'] + ]); + } + private function get(string $endpoint, array $params = []): array { try { diff --git a/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php b/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php index 5c50d82..04d3e16 100644 --- a/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php +++ b/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php @@ -55,13 +55,30 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface public function save(DomainManga $manga): void { - $entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue()) - ?? new EntityManga(); - - $this->updateEntity($entity, $manga); + $entity = new EntityManga(); + $entity->setTitle($manga->getTitle()->getValue()) + ->setSlug($manga->getSlug()->getValue()) + ->setDescription($manga->getDescription()) + ->setAuthor($manga->getAuthor()) + ->setPublicationYear($manga->getPublicationYear()) + ->setGenres($manga->getGenres()) + ->setStatus($manga->getStatus()) + ->setExternalId($manga->getExternalId()->getValue()) + ->setImageUrl($manga->getImageUrl()) + ->setMonitored(false); + + if ($manga->getRating() !== null) { + $entity->setRating($manga->getRating()); + } + $this->entityManager->persist($entity); $this->entityManager->flush(); + + // Met à jour l'ID du modèle du domaine avec l'ID généré par la BDD + if ($entity->getId()) { + $manga->updateId(new MangaId((string) $entity->getId())); + } } public function delete(DomainManga $manga): void @@ -121,29 +138,6 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface ); } - private function updateEntity(EntityManga $entity, DomainManga $manga): void - { - $entity->setTitle($manga->getTitle()->getValue()) - ->setSlug($manga->getSlug()->getValue()) - ->setDescription($manga->getDescription()) - ->setAuthor($manga->getAuthor()) - ->setPublicationYear($manga->getPublicationYear()) - ->setGenres($manga->getGenres()) - ->setStatus($manga->getStatus()); - - if ($manga->getExternalId()) { - $entity->setExternalId($manga->getExternalId()->getValue()); - } - - if ($manga->getImageUrl()) { - $entity->setImageUrl($manga->getImageUrl()); - } - - if ($manga->getRating()) { - $entity->setRating($manga->getRating()); - } - } - private function toChapterDomain(EntityChapter $entity): Chapter { return new Chapter( diff --git a/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php b/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php index a48bc6c..37a77a0 100644 --- a/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php +++ b/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php @@ -123,4 +123,25 @@ readonly class MangadexProvider implements MangaProviderInterface } } } + + public function findByExternalId(ExternalId $externalId): ?Manga + { + try { + $result = $this->client->getManga($externalId->getValue()); + + if (!isset($result['data'])) { + return null; + } + + $manga = $this->createMangaFromResult($result['data']); + + if ($manga) { + $this->enrichWithRatings([$manga]); + } + + return $manga; + } catch (\Exception) { + return null; + } + } } \ No newline at end of file diff --git a/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php b/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php index d06ad84..8325234 100644 --- a/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php +++ b/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php @@ -5,6 +5,7 @@ namespace App\Tests\Domain\Manga\Adapter; use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface; use App\Domain\Manga\Domain\Model\Manga; use App\Domain\Manga\Domain\Model\MangaCollection; +use App\Domain\Manga\Domain\Model\ValueObject\ExternalId; class InMemoryMangaProvider implements MangaProviderInterface { @@ -31,4 +32,15 @@ class InMemoryMangaProvider implements MangaProviderInterface return new MangaCollection($results); } + + public function findByExternalId(ExternalId $externalId): ?Manga + { + foreach ($this->mangas as $manga) { + if ($manga->getExternalId() && $manga->getExternalId()->getValue() === $externalId->getValue()) { + return $manga; + } + } + + return null; + } } \ No newline at end of file diff --git a/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php b/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php new file mode 100644 index 0000000..1996aea --- /dev/null +++ b/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php @@ -0,0 +1,123 @@ +mangas = $mangas; + $this->feeds = $feeds; + $this->aggregates = $aggregates; + } + + public function authenticate(): void + { + // No need to implement for tests + } + + public function refreshToken(): void + { + // No need to implement for tests + } + + public function searchManga(string $title): array + { + $results = []; + foreach ($this->mangas as $id => $manga) { + if (isset($manga['attributes']['title']['en']) && + str_contains( + strtolower($manga['attributes']['title']['en']), + strtolower($title) + ) + ) { + $results[] = array_merge(['id' => $id], $manga); + } + } + + return ['data' => $results]; + } + + public function getMangaRatings(array $mangaIds): array + { + $statistics = []; + foreach ($mangaIds as $id) { + $statistics[$id] = [ + 'rating' => ['average' => 4.5] // Default rating for tests + ]; + } + + return ['statistics' => $statistics]; + } + + public function getMangaFeed(string $mangaId, int $offset = 0, int $limit = 500, string $order = 'asc'): array + { + if (!isset($this->feeds[$mangaId])) { + return [ + 'data' => [], + 'total' => 0 + ]; + } + + $feed = $this->feeds[$mangaId]; + if ($order === 'desc') { + $feed = array_reverse($feed); + } + + return [ + 'data' => array_slice($feed, $offset, $limit), + 'total' => count($feed) + ]; + } + + public function getMangaAggregate(string $mangaId): array + { + if (!isset($this->aggregates[$mangaId])) { + return [ + 'result' => 'ok', + 'volumes' => [] + ]; + } + + return [ + 'result' => 'ok', + 'volumes' => $this->aggregates[$mangaId] + ]; + } + + public function getManga(string $mangaId): array + { + if (!isset($this->mangas[$mangaId])) { + return ['data' => null]; + } + + return [ + 'result' => 'ok', + 'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId]) + ]; + } + + public function addManga(string $id, array $data): void + { + $this->mangas[$id] = $data; + } + + public function addFeed(string $mangaId, array $feed): void + { + $this->feeds[$mangaId] = $feed; + } + + public function addAggregate(string $mangaId, array $aggregate): void + { + $this->aggregates[$mangaId] = $aggregate; + } +} \ No newline at end of file diff --git a/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php new file mode 100644 index 0000000..04b3103 --- /dev/null +++ b/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php @@ -0,0 +1,80 @@ +provider = new InMemoryMangaProvider(); + $this->repository = new InMemoryMangaRepository(); + $this->handler = new CreateMangaFromMangadexHandler( + $this->provider, + $this->repository + ); + } + + public function testHandleSuccess(): void + { + // Arrange + $externalId = 'manga-123'; + $manga = new Manga( + new MangaId('123'), + new MangaTitle('Test Manga'), + new MangaSlug('test-manga'), + 'Description', + 'Author', + 2020, + ['action'], + 'ongoing', + new ExternalId($externalId), + 'http://example.com/cover.jpg', + 4.5 + ); + + $this->provider = new InMemoryMangaProvider([$manga]); + $this->handler = new CreateMangaFromMangadexHandler( + $this->provider, + $this->repository + ); + + // Act + $command = new CreateMangaFromMangadex($externalId); + $this->handler->handle($command); + + // Assert + $savedManga = $this->repository->findById('123'); + $this->assertNotNull($savedManga); + $this->assertEquals($externalId, $savedManga->getExternalId()->getValue()); + $this->assertEquals('Test Manga', $savedManga->getTitle()->getValue()); + } + + public function testHandleThrowsExceptionWhenMangaNotFound(): void + { + // Arrange + $command = new CreateMangaFromMangadex('non-existent-manga'); + + // Assert + $this->expectException(MangaNotFoundException::class); + $this->expectExceptionMessage('Manga not found on Mangadex'); + + // Act + $this->handler->handle($command); + } +} \ No newline at end of file