feat: endpoint FetchMangaChapters et tests
This commit is contained in:
parent
3dc0a0b406
commit
879b8fa2dc
10
src/Domain/Manga/Application/Command/FetchMangaChapters.php
Normal file
10
src/Domain/Manga/Application/Command/FetchMangaChapters.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\Command;
|
||||
|
||||
readonly class FetchMangaChapters
|
||||
{
|
||||
public function __construct(
|
||||
public string $externalId
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\FetchMangaChapters;
|
||||
use App\Domain\Manga\Domain\Contract\Client\MangadexClientInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
readonly class FetchMangaChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangadexClientInterface $mangadexClient,
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
) {}
|
||||
|
||||
public function handle(FetchMangaChapters $command): void
|
||||
{
|
||||
$manga = $this->mangaRepository->findByExternalId(new ExternalId($command->externalId));
|
||||
|
||||
if ($manga === null) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
$limit = 500;
|
||||
$hasMore = true;
|
||||
|
||||
while ($hasMore) {
|
||||
$feed = $this->mangadexClient->getMangaFeed(
|
||||
$command->externalId,
|
||||
$offset,
|
||||
$limit
|
||||
);
|
||||
|
||||
foreach ($feed['data'] as $chapterData) {
|
||||
$chapter = new Chapter(
|
||||
new ChapterId((string) Uuid::uuid4()),
|
||||
$manga->getId()->getValue(),
|
||||
(float) $chapterData['attributes']['chapter'],
|
||||
$chapterData['attributes']['title'],
|
||||
isset($chapterData['attributes']['volume']) ? (int) $chapterData['attributes']['volume'] : null,
|
||||
true,
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
$this->mangaRepository->saveChapter($chapter);
|
||||
}
|
||||
|
||||
$offset += $limit;
|
||||
$hasMore = count($feed['data']) === $limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Domain\Manga\Domain\Contract\Repository;
|
||||
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
|
||||
interface MangaRepositoryInterface
|
||||
{
|
||||
@@ -13,4 +15,6 @@ interface MangaRepositoryInterface
|
||||
public function delete(Manga $manga): void;
|
||||
public function findChapters(string $mangaId, int $page = 1, int $limit = 20, string $sortOrder = 'desc'): array;
|
||||
public function countChapters(string $mangaId): int;
|
||||
public function findByExternalId(ExternalId $externalId): ?Manga;
|
||||
public function saveChapter(Chapter $chapter): void;
|
||||
}
|
||||
@@ -21,6 +21,11 @@ readonly class Chapter
|
||||
return $this->id->getValue();
|
||||
}
|
||||
|
||||
public function getMangaId(): string
|
||||
{
|
||||
return $this->mangaId;
|
||||
}
|
||||
|
||||
public function getNumber(): float
|
||||
{
|
||||
return $this->number;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\FetchMangaChaptersProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'FetchMangaChapters',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/manga/chapters/fetch',
|
||||
processor: FetchMangaChaptersProcessor::class,
|
||||
status: 202
|
||||
)
|
||||
]
|
||||
)]
|
||||
class FetchMangaChaptersResource
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\NotBlank]
|
||||
public string $externalId
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Domain\Manga\Application\Command\FetchMangaChapters;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\FetchMangaChaptersResource;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
readonly class FetchMangaChaptersProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MessageBusInterface $messageBus
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
|
||||
{
|
||||
if (!$data instanceof FetchMangaChaptersResource) {
|
||||
throw new \InvalidArgumentException('Invalid resource type');
|
||||
}
|
||||
|
||||
$this->messageBus->dispatch(
|
||||
new FetchMangaChapters($data->externalId)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\FetchMangaChapters;
|
||||
use App\Domain\Manga\Application\CommandHandler\FetchMangaChaptersHandler;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler]
|
||||
readonly class SymfonyFetchMangaChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private FetchMangaChaptersHandler $handler
|
||||
) {}
|
||||
|
||||
public function __invoke(FetchMangaChapters $command): void
|
||||
{
|
||||
$this->handler->handle($command);
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,34 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findByExternalId(ExternalId $externalId): ?DomainManga
|
||||
{
|
||||
$entity = $this->entityManager->getRepository(EntityManga::class)->findOneBy([
|
||||
'externalId' => $externalId->getValue()
|
||||
]);
|
||||
|
||||
return $entity ? $this->toDomain($entity) : null;
|
||||
}
|
||||
|
||||
public function saveChapter(Chapter $chapter): void
|
||||
{
|
||||
$manga = $this->entityManager->find(EntityManga::class, $chapter->getMangaId());
|
||||
|
||||
if (!$manga) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
|
||||
$entity = new EntityChapter();
|
||||
$entity->setManga($manga)
|
||||
->setNumber($chapter->getNumber())
|
||||
->setTitle($chapter->getTitle())
|
||||
->setVolume($chapter->getVolume())
|
||||
->setVisible($chapter->isVisible());
|
||||
|
||||
$this->entityManager->persist($entity);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
private function toDomain(EntityManga $entity): DomainManga
|
||||
{
|
||||
return new DomainManga(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Scraping\Infrastructure\Handler;
|
||||
namespace App\Domain\Scraping\Infrastructure\CommandHandler;
|
||||
|
||||
use App\Domain\Scraping\Application\Command\ScrapeChapter;
|
||||
use App\Domain\Scraping\Application\CommandHandler\ScrapeChapterHandler;
|
||||
Reference in New Issue
Block a user