feat: CreateMangaFromMangadex endpoint + tests, missing image saving

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-02-11 00:10:54 +01:00
parent ae0eac3197
commit 50080f9779
14 changed files with 387 additions and 29 deletions

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Domain\Manga\Application\Command;
readonly class CreateMangaFromMangadex
{
public function __construct(
public string $externalId
) {}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Domain\Manga\Application\CommandHandler;
use App\Domain\Manga\Application\Command\CreateMangaFromMangadex;
use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface;
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
readonly class CreateMangaFromMangadexHandler
{
public function __construct(
private MangaProviderInterface $mangaProvider,
private MangaRepositoryInterface $mangaRepository
) {}
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');
}
$this->mangaRepository->save($manga);
}
}

View File

@@ -72,4 +72,25 @@ interface MangadexClientInterface
* }
*/
public function getMangaAggregate(string $mangaId): array;
/**
* @return array{
* result: string,
* data: array{
* id: string,
* attributes: array{
* title: array<string, string>,
* description: array<string, string>,
* year: ?int,
* status: string,
* tags: array<array{attributes: array{name: array<string, string>}}>
* },
* relationships: array<array{
* type: string,
* attributes: array{name: string|null, fileName: string|null}
* }>
* }
* }
*/
public function getManga(string $mangaId): array;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -82,4 +82,9 @@ final class Manga
{
$this->rating = $rating;
}
public function updateId(MangaId $id): void
{
$this->id = $id;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaProcessor;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
shortName: 'CreateManga',
operations: [
new Post(
uriTemplate: '/mangas/create-from-mangadex',
processor: CreateMangaProcessor::class,
openapiContext: [
'summary' => '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;
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Domain\Manga\Application\Command\CreateMangaFromMangadex;
use App\Domain\Manga\Application\CommandHandler\CreateMangaFromMangadexHandler;
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class CreateMangaProcessor implements ProcessorInterface
{
public function __construct(
private CreateMangaFromMangadexHandler $handler
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
{
try {
$this->handler->handle(new CreateMangaFromMangadex($data->externalId));
} catch (MangaNotFoundException $e) {
throw new NotFoundHttpException($e->getMessage());
}
}
}

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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;
}
}
}