feat: ajout de la gestion des chapitres de manga, incluant la récupération et la sauvegarde des chapitres en français et en anglais, ainsi que l'optimisation de la logique de sauvegarde pour éviter les doublons
This commit is contained in:
parent
34dfa57dc0
commit
0111f1b5f1
@@ -20,7 +20,7 @@ readonly class FetchMangaChaptersHandler
|
||||
public function handle(FetchMangaChapters $command): void
|
||||
{
|
||||
$manga = $this->mangaRepository->findByExternalId(new ExternalId($command->externalId));
|
||||
|
||||
|
||||
if ($manga === null) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
@@ -28,6 +28,8 @@ readonly class FetchMangaChaptersHandler
|
||||
$offset = 0;
|
||||
$limit = 500;
|
||||
$hasMore = true;
|
||||
$chaptersByNumber = [];
|
||||
$chapterNumbers = [];
|
||||
|
||||
while ($hasMore) {
|
||||
$feed = $this->mangadexClient->getMangaFeed(
|
||||
@@ -37,22 +39,47 @@ readonly class FetchMangaChaptersHandler
|
||||
);
|
||||
|
||||
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,
|
||||
false,
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
$chapterNumber = (float) $chapterData['attributes']['chapter'];
|
||||
$language = $chapterData['attributes']['translatedLanguage'];
|
||||
|
||||
$this->mangaRepository->saveChapter($chapter);
|
||||
// On ne traite que les chapitres en français ou en anglais
|
||||
if (!in_array($language, ['fr', 'en'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Si le chapitre n'existe pas encore ou si c'est une version française
|
||||
if (!isset($chaptersByNumber[$chapterNumber]) || $language === 'fr') {
|
||||
$chapter = new Chapter(
|
||||
new ChapterId((string) Uuid::uuid4()),
|
||||
$manga->getId()->getValue(),
|
||||
$chapterNumber,
|
||||
$chapterData['attributes']['title'],
|
||||
isset($chapterData['attributes']['volume']) ? (int) $chapterData['attributes']['volume'] : null,
|
||||
true,
|
||||
false,
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
$chaptersByNumber[$chapterNumber] = $chapter;
|
||||
$chapterNumbers[] = $chapterNumber;
|
||||
}
|
||||
}
|
||||
|
||||
$offset += $limit;
|
||||
$hasMore = count($feed['data']) === $limit;
|
||||
}
|
||||
|
||||
// Récupère les chapitres existants
|
||||
$existingChapters = $this->mangaRepository->findExistingChaptersByNumbers(
|
||||
$manga->getId()->getValue(),
|
||||
$chapterNumbers
|
||||
);
|
||||
|
||||
// Sauvegarde uniquement les nouveaux chapitres
|
||||
foreach ($chaptersByNumber as $chapterNumber => $chapter) {
|
||||
if (!isset($existingChapters[$chapterNumber])) {
|
||||
$this->mangaRepository->saveChapter($chapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,4 +21,5 @@ interface MangaRepositoryInterface
|
||||
public function findBySlug(MangaSlug $slug): ?Manga;
|
||||
public function search(string $query, int $page = 1, int $limit = 20): array;
|
||||
public function countSearch(string $query): int;
|
||||
}
|
||||
public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array;
|
||||
}
|
||||
|
||||
@@ -200,6 +200,26 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array
|
||||
{
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(EntityChapter::class, 'c')
|
||||
->where('c.manga = :mangaId')
|
||||
->andWhere('c.number IN (:chapterNumbers)')
|
||||
->setParameter('mangaId', $mangaId)
|
||||
->setParameter('chapterNumbers', $chapterNumbers);
|
||||
|
||||
$chapters = $queryBuilder->getQuery()->getResult();
|
||||
|
||||
$chaptersByNumber = [];
|
||||
foreach ($chapters as $chapter) {
|
||||
$chaptersByNumber[$chapter->getNumber()] = $this->toChapterDomain($chapter);
|
||||
}
|
||||
|
||||
return $chaptersByNumber;
|
||||
}
|
||||
|
||||
private function toDomain(EntityManga $entity): DomainManga
|
||||
{
|
||||
return new DomainManga(
|
||||
|
||||
@@ -5,14 +5,12 @@ namespace App\Domain\Scraping\Application\CommandHandler;
|
||||
use App\Domain\Scraping\Application\Command\ScrapeChapter;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\ScrapingJobRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\SourceRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Service\CbzGeneratorInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Service\ImageDownloaderInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface;
|
||||
use App\Domain\Scraping\Domain\Event\ChapterScraped;
|
||||
use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed;
|
||||
use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted;
|
||||
use App\Domain\Scraping\Domain\Model\ScrapingJob;
|
||||
use App\Domain\Scraping\Domain\Model\ValueObject\CbzGenerationRequest;
|
||||
use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest;
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Domain\Scraping\Domain\Event;
|
||||
|
||||
class ChapterScraped
|
||||
readonly class ChapterScraped
|
||||
{
|
||||
public function __construct(
|
||||
private string $jobId
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $getId
|
||||
*/
|
||||
public function __construct(string $getId)
|
||||
public function getJobId(): string
|
||||
{
|
||||
return $this->jobId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Domain\Shared\Application\Query\ListJobsQuery;
|
||||
use App\Domain\Shared\Application\QueryHandler\ListJobsQueryHandler;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
use App\Domain\Shared\Infrastructure\ApiPlatform\Resource\GetJobListResource;
|
||||
use ApiPlatform\State\Pagination\ArrayPaginator;
|
||||
|
||||
readonly class GetJobListStateProvider implements ProviderInterface
|
||||
{
|
||||
@@ -17,7 +18,7 @@ readonly class GetJobListStateProvider implements ProviderInterface
|
||||
private ListJobsQueryHandler $handler
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ArrayPaginator
|
||||
{
|
||||
$filters = $context['filters'] ?? [];
|
||||
|
||||
@@ -60,15 +61,13 @@ readonly class GetJobListStateProvider implements ProviderInterface
|
||||
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
return [
|
||||
'items' => array_map(
|
||||
return new ArrayPaginator(
|
||||
array_map(
|
||||
fn($job) => GetJobListResource::fromJob($job),
|
||||
$response->items
|
||||
),
|
||||
'total' => $response->total,
|
||||
'page' => $response->page,
|
||||
'limit' => $response->limit,
|
||||
'pages' => $response->pages
|
||||
];
|
||||
0,
|
||||
$response->total
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,10 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface
|
||||
|
||||
public function save(Job $job): void
|
||||
{
|
||||
dump('save', $job);
|
||||
/** @var JobEntity|null $existingJobEntity */
|
||||
$existingJobEntity = $this->entityManager->find(JobEntity::class, $job->id);
|
||||
|
||||
if ($existingJobEntity) {
|
||||
dump('existingJobEntity', $existingJobEntity);
|
||||
$existingJobEntity->setStatus($job->status->value);
|
||||
$existingJobEntity->setStartedAt($job->startedAt);
|
||||
$existingJobEntity->setCompletedAt($job->completedAt);
|
||||
@@ -33,14 +31,11 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface
|
||||
$existingJobEntity->setAttempts($job->attempts);
|
||||
$existingJobEntity->setContext($job->context);
|
||||
$this->entityManager->persist($existingJobEntity);
|
||||
dump('updated', $existingJobEntity);
|
||||
} else {
|
||||
$entity = $this->mapper->toEntity($job);
|
||||
$this->entityManager->persist($entity);
|
||||
dump('created', $entity);
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
dump('flushed');
|
||||
}
|
||||
|
||||
public function get(string $id): Job
|
||||
|
||||
Reference in New Issue
Block a user