feat: refactorisation de la gestion du scraping des chapitres en remplaçant les identifiants de manga et de chapitre par un identifiant de chapitre unique, amélioration de la récupération des sources préférées et ajout de la gestion des erreurs pour les échecs de scraping.

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-04-03 16:34:30 +02:00
parent e29433bb0c
commit c9f1771522
15 changed files with 270 additions and 104 deletions

View File

@@ -21,15 +21,9 @@ use Symfony\Component\Validator\Constraints as Assert;
readonly class ScrapeChapterRequest
{
public function __construct(
#[ApiProperty(description: 'ID du manga')]
#[ApiProperty(description: 'ID du chapitre à scraper')]
#[Assert\NotBlank]
public string $mangaId,
#[ApiProperty(description: 'Numéro du chapitre')]
#[Assert\NotBlank]
public string $chapterNumber,
#[ApiProperty(description: 'ID de la source')]
#[Assert\NotBlank]
public string $sourceId,
public string $chapterId,
) {
}
}

View File

@@ -22,9 +22,7 @@ final class ScrapeChapterStateProcessor implements ProcessorInterface
{
$this->commandBus->dispatch(
new ScrapeChapter(
$data->mangaId,
$data->chapterNumber,
$data->sourceId
$data->chapterId
)
);
}

View File

@@ -13,6 +13,26 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
private EntityManagerInterface $entityManager,
) {}
/**
* Récupère un chapitre par son identifiant
*/
public function getById(string $id): ?Chapter
{
$chapterEntity = $this->entityManager->getRepository(EntityChapter::class)->find($id);
if (!$chapterEntity) {
return null;
}
return new Chapter(
id: $chapterEntity->getId(),
mangaId: $chapterEntity->getManga()->getId(),
chapterNumber: $chapterEntity->getNumber(),
volumeNumber: $chapterEntity->getVolume(),
cbzPath: $chapterEntity->getCbzPath(),
);
}
/**
* @throws ChapterNotFoundException
*/
@@ -32,6 +52,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
mangaId: $chapterEntity->getManga()->getId(),
chapterNumber: $chapterEntity->getNumber(),
volumeNumber: $chapterEntity->getVolume(),
cbzPath: $chapterEntity->getCbzPath(),
);
}

View File

@@ -19,13 +19,24 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
/** @var EntityManga|null $mangaEntity */
$mangaEntity = $this->entityManager->getRepository(EntityManga::class)->find($id);
return $mangaEntity ? new Manga(
if (!$mangaEntity) {
return null;
}
// Récupération des sources préférées
$preferredSourceIds = [];
foreach ($mangaEntity->getPreferredSources() as $source) {
$preferredSourceIds[] = $source->getId();
}
return new Manga(
$mangaEntity->getId(),
$mangaEntity->getTitle(),
$mangaEntity->getSlug(),
$mangaEntity->getDescription(),
$mangaEntity->getAuthor(),
$mangaEntity->getPublicationYear(),
) : null;
$mangaEntity->getDescription() ?? '',
$mangaEntity->getAuthor() ?? '',
$mangaEntity->getPublicationYear() ?? '',
$preferredSourceIds,
);
}
}

View File

@@ -46,4 +46,43 @@ readonly class LegacySourceRepository implements SourceRepositoryInterface
updatedAt: new DateTimeImmutable()
);
}
/**
* @return Source[]
*/
public function getAll(): array
{
/** @var ContentSource[] $sourceEntities */
$sourceEntities = $this->entityManager->getRepository(ContentSource::class)->findAll();
$sources = [];
foreach ($sourceEntities as $sourceEntity) {
$sources[] = $this->convertEntityToModel($sourceEntity);
}
return $sources;
}
/**
* Convertit une entité ContentSource en modèle Source
*/
private function convertEntityToModel(ContentSource $source): Source
{
return new Source(
id: new SourceId($source->getId()),
name: $source->getCleanBaseUrl(),
description: 'Legacy Source: ' . $source->getBaseUrl(),
baseUrl: $source->getBaseUrl(),
scrappingParameters: [
'imageSelector' => $source->getImageSelector(),
'nextPageSelector' => $source->getNextPageSelector(),
'chapterUrlFormat' => $source->getChapterUrlFormat(),
'scrapingType' => $source->getScrapingType(),
'chapterSelector' => $source->getChapterSelector()
],
isActive: true,
createdAt: new DateTimeImmutable(),
updatedAt: new DateTimeImmutable()
);
}
}