Merge branch 'main' of ssh://git.homelab.nestor-server.fr:2222/colgora/Mangarr
All checks were successful
Build and Deploy / deploy (push) Successful in 1m46s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m46s
# Conflicts: # src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php # src/Domain/Manga/Application/CommandHandler/EditMultipleChaptersHandler.php # src/Domain/Manga/Application/EventListener/ChapterImportedEventListener.php # src/Domain/Manga/Application/EventListener/VolumeImportedEventListener.php # src/Domain/Manga/Application/Response/ChapterResponse.php # src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteCbzProvider.php # src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteChapterProvider.php # src/Domain/Manga/Infrastructure/Persistence/Repository/LegacyChapterRepository.php
This commit is contained in:
@@ -4,7 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\DeleteCbzResource;
|
||||
@@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
readonly class DeleteCbzProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ChapterRepositoryInterface $chapterRepository
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ readonly class DeleteCbzProvider implements ProviderInterface
|
||||
$chapterId = $uriVariables['id'];
|
||||
|
||||
try {
|
||||
$chapter = $this->chapterRepository->findVisibleById($chapterId);
|
||||
$chapter = $this->mangaRepository->findVisibleChapterById($chapterId);
|
||||
|
||||
if (!$chapter) {
|
||||
throw new ChapterNotFoundException($chapterId);
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\DeleteChapterResource;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@@ -12,7 +12,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
readonly class DeleteChapterProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ChapterRepositoryInterface $chapterRepository
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ readonly class DeleteChapterProvider implements ProviderInterface
|
||||
$chapterId = $uriVariables['id'];
|
||||
|
||||
try {
|
||||
$chapter = $this->chapterRepository->findVisibleById($chapterId);
|
||||
$chapter = $this->mangaRepository->findVisibleChapterById($chapterId);
|
||||
|
||||
if (!$chapter) {
|
||||
throw new ChapterNotFoundException($chapterId);
|
||||
|
||||
@@ -53,8 +53,8 @@ readonly class GetMangaChaptersStateProvider implements ProviderInterface
|
||||
title: $chapter->title,
|
||||
volume: $chapter->volume,
|
||||
isVisible: $chapter->isVisible,
|
||||
isAvailable: $chapter->cbzPath !== null,
|
||||
createdAt: $chapter->createdAt->format(\DateTimeInterface::RFC3339)
|
||||
isAvailable: $chapter->pagesDirectory !== null,
|
||||
createdAt: $chapter->createdAt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,44 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
if ($entity->getId()) {
|
||||
$manga->updateId(new MangaId((string) $entity->getId()));
|
||||
}
|
||||
|
||||
// Handle new chapters added through the aggregate
|
||||
foreach ($manga->pullNewChapters() as $chapter) {
|
||||
$mangaEntity = $this->entityManager->find(EntityManga::class, (int) $manga->getId()->getValue());
|
||||
$chapterEntity = new EntityChapter();
|
||||
$chapterEntity->setManga($mangaEntity)
|
||||
->setNumber($chapter->getNumber())
|
||||
->setTitle($chapter->getTitle())
|
||||
->setVolume($chapter->getVolume())
|
||||
->setVisible($chapter->isVisible())
|
||||
->setPagesDirectory($chapter->getPagesDirectory())
|
||||
->setPageCount($chapter->getPageCount());
|
||||
$this->entityManager->persist($chapterEntity);
|
||||
}
|
||||
|
||||
// Handle chapters modified through the aggregate
|
||||
foreach ($manga->pullModifiedChapters() as $chapter) {
|
||||
$chapterEntity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
|
||||
if ($chapterEntity) {
|
||||
$chapterEntity->setVisible($chapter->isVisible())
|
||||
->setPagesDirectory($chapter->getPagesDirectory())
|
||||
->setPageCount($chapter->getPageCount())
|
||||
->setTitle($chapter->getTitle())
|
||||
->setVolume($chapter->getVolume())
|
||||
->setCbzPath($chapter->getPagesDirectory());
|
||||
$this->entityManager->persist($chapterEntity);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle chapters deleted through the aggregate
|
||||
foreach ($manga->pullChaptersToDelete() as $chapter) {
|
||||
$chapterEntity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
|
||||
if ($chapterEntity) {
|
||||
$this->entityManager->remove($chapterEntity);
|
||||
}
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function delete(DomainManga $manga): void
|
||||
@@ -167,25 +205,75 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
return $entity ? $this->toDomain($entity) : null;
|
||||
}
|
||||
|
||||
public function saveChapter(Chapter $chapter): ChapterId
|
||||
public function findChapterById(string $id): ?Chapter
|
||||
{
|
||||
$manga = $this->entityManager->find(EntityManga::class, $chapter->getMangaId());
|
||||
$entity = $this->entityManager->find(EntityChapter::class, $id);
|
||||
|
||||
if (!$manga) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
return $entity ? $this->toChapterDomain($entity) : null;
|
||||
}
|
||||
|
||||
$entity = new EntityChapter();
|
||||
$entity->setManga($manga)
|
||||
->setNumber($chapter->getNumber())
|
||||
->setTitle($chapter->getTitle())
|
||||
->setVolume($chapter->getVolume())
|
||||
->setVisible($chapter->isVisible());
|
||||
public function findVisibleChapterById(string $id): ?Chapter
|
||||
{
|
||||
$entity = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(EntityChapter::class, 'c')
|
||||
->where('c.id = :id')
|
||||
->andWhere('c.visible = :visible')
|
||||
->setParameter('id', $id)
|
||||
->setParameter('visible', 1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
|
||||
$this->entityManager->persist($entity);
|
||||
$this->entityManager->flush();
|
||||
return $entity ? $this->toChapterDomain($entity) : null;
|
||||
}
|
||||
|
||||
return new ChapterId((string) $entity->getId());
|
||||
public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter
|
||||
{
|
||||
$entity = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(EntityChapter::class, 'c')
|
||||
->where('c.manga = :mangaId')
|
||||
->andWhere('c.number = :chapterNumber')
|
||||
->setParameter('mangaId', $mangaId)
|
||||
->setParameter('chapterNumber', $chapterNumber)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
|
||||
return $entity ? $this->toChapterDomain($entity) : null;
|
||||
}
|
||||
|
||||
public function findChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
|
||||
{
|
||||
$entities = $this->entityManager->getRepository(EntityChapter::class)
|
||||
->findBy(['manga' => $mangaId, 'volume' => $volume]);
|
||||
|
||||
return array_map([$this, 'toChapterDomain'], $entities);
|
||||
}
|
||||
|
||||
public function findVisibleChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
|
||||
{
|
||||
$entities = $this->entityManager->getRepository(EntityChapter::class)
|
||||
->findBy(['manga' => $mangaId, 'volume' => $volume, 'visible' => true]);
|
||||
|
||||
return array_map([$this, 'toChapterDomain'], $entities);
|
||||
}
|
||||
|
||||
public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array
|
||||
{
|
||||
$entities = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(EntityChapter::class, 'c')
|
||||
->where('c.manga = :mangaId')
|
||||
->andWhere('c.volume = :volume')
|
||||
->andWhere('c.visible = true')
|
||||
->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL')
|
||||
->setParameter('mangaId', $mangaId)
|
||||
->setParameter('volume', $volume)
|
||||
->orderBy('c.number', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map([$this, 'toChapterDomain'], $entities);
|
||||
}
|
||||
|
||||
public function search(string $query, int $page = 1, int $limit = 20): array
|
||||
@@ -315,12 +403,14 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
{
|
||||
return new Chapter(
|
||||
id: new ChapterId((string) $entity->getId()),
|
||||
mangaId: $entity->getManga()->getId(),
|
||||
mangaId: new MangaId((string) $entity->getManga()->getId()),
|
||||
number: $entity->getNumber(),
|
||||
title: $entity->getTitle(),
|
||||
volume: $entity->getVolume(),
|
||||
isVisible: $entity->isVisible(),
|
||||
cbzPath: $entity->getCbzPath()
|
||||
// Fallback to cbzPath during transition (Phase 4 will drop cbzPath column)
|
||||
pagesDirectory: $entity->getPagesDirectory() ?? $entity->getCbzPath(),
|
||||
pageCount: $entity->getPageCount(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\Persistence\Repository;
|
||||
|
||||
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
use App\Entity\Chapter as ChapterEntity;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
readonly class LegacyChapterRepository implements ChapterRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager
|
||||
) {
|
||||
}
|
||||
|
||||
public function findById(string $id): ?Chapter
|
||||
{
|
||||
$entity = $this->entityManager->find(ChapterEntity::class, $id);
|
||||
|
||||
return $entity ? $this->toDomainModel($entity) : null;
|
||||
}
|
||||
|
||||
public function findVisibleById(string $id): ?Chapter
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(ChapterEntity::class, 'c')
|
||||
->where('c.id = :id')
|
||||
->andWhere('c.visible = :visible')
|
||||
->setParameter('id', $id)
|
||||
->setParameter('visible', 1);
|
||||
|
||||
$entity = $qb->getQuery()->getOneOrNullResult();
|
||||
|
||||
return $entity ? $this->toDomainModel($entity) : null;
|
||||
}
|
||||
|
||||
public function findByMangaIdAndChapterNumber(string $mangaId, float $chapterNumber): ?Chapter
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(ChapterEntity::class, 'c')
|
||||
->where('c.manga = :mangaId')
|
||||
->andWhere('c.number = :chapterNumber')
|
||||
->setParameter('mangaId', $mangaId)
|
||||
->setParameter('chapterNumber', $chapterNumber);
|
||||
|
||||
$entity = $qb->getQuery()->getOneOrNullResult();
|
||||
|
||||
return $entity ? $this->toDomainModel($entity) : null;
|
||||
}
|
||||
|
||||
public function save(Chapter $chapter): void
|
||||
{
|
||||
$entity = $this->entityManager->find(ChapterEntity::class, $chapter->getId());
|
||||
|
||||
if (!$entity) {
|
||||
throw new \RuntimeException(sprintf('Chapter with id %s not found', $chapter->getId()));
|
||||
}
|
||||
|
||||
$entity->setVisible($chapter->isVisible());
|
||||
$entity->setCbzPath($chapter->getCbzPath());
|
||||
$entity->setTitle($chapter->getTitle());
|
||||
$entity->setVolume($chapter->getVolume());
|
||||
|
||||
$this->entityManager->persist($entity);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function delete(Chapter $chapter): void
|
||||
{
|
||||
$entity = $this->entityManager->find(ChapterEntity::class, $chapter->getId());
|
||||
|
||||
if ($entity) {
|
||||
$this->entityManager->remove($entity);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function findByMangaIdAndVolume(string $mangaId, int $volume): array
|
||||
{
|
||||
$entities = $this->entityManager->getRepository(ChapterEntity::class)
|
||||
->findBy(['manga' => $mangaId, 'volume' => $volume]);
|
||||
|
||||
return array_map([$this, 'toDomainModel'], $entities);
|
||||
}
|
||||
|
||||
public function findVisibleByMangaIdAndVolume(string $mangaId, int $volume): array
|
||||
{
|
||||
$entities = $this->entityManager->getRepository(ChapterEntity::class)
|
||||
->findBy(['manga' => $mangaId, 'volume' => $volume, 'visible' => true]);
|
||||
|
||||
return array_map([$this, 'toDomainModel'], $entities);
|
||||
}
|
||||
|
||||
public function findVisibleWithCbzByMangaIdAndVolume(string $mangaId, int $volume): array
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(ChapterEntity::class, 'c')
|
||||
->where('c.manga = :mangaId')
|
||||
->andWhere('c.volume = :volume')
|
||||
->andWhere('c.visible = true')
|
||||
->andWhere('c.cbzPath IS NOT NULL')
|
||||
->setParameter('mangaId', $mangaId)
|
||||
->setParameter('volume', $volume)
|
||||
->orderBy('c.number', 'ASC');
|
||||
|
||||
$entities = $qb->getQuery()->getResult();
|
||||
|
||||
return array_map([$this, 'toDomainModel'], $entities);
|
||||
}
|
||||
|
||||
private function toDomainModel(ChapterEntity $entity): Chapter
|
||||
{
|
||||
return new Chapter(
|
||||
new ChapterId((string) $entity->getId()),
|
||||
(string) $entity->getManga()->getId(),
|
||||
$entity->getNumber(),
|
||||
$entity->getTitle(),
|
||||
$entity->getVolume(),
|
||||
$entity->isVisible(),
|
||||
$entity->getCbzPath(),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -68,12 +68,13 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz
|
||||
if ($shouldReplaceChapter) {
|
||||
$chaptersByNumber[(string) $chapterNumber] = new Chapter(
|
||||
new ChapterId((string) Uuid::uuid4()),
|
||||
$manga->getId()->getValue(),
|
||||
$manga->getId(),
|
||||
$chapterNumber,
|
||||
$title,
|
||||
isset($chapterData['attributes']['volume']) ? (int) $chapterData['attributes']['volume'] : null,
|
||||
true,
|
||||
null,
|
||||
0,
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
$chapterLanguages[(string) $chapterNumber] = $language;
|
||||
@@ -98,8 +99,8 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz
|
||||
// Sauvegarde uniquement les nouveaux chapitres et collecte leurs IDs
|
||||
foreach ($chaptersByNumber as $chapterNumber => $chapter) {
|
||||
if (!isset($existingChapters[(float) $chapterNumber])) {
|
||||
$newChapterId = $this->mangaRepository->saveChapter($chapter);
|
||||
$newChapterIds[] = $newChapterId->getValue(); // ✨ Collecte des IDs
|
||||
$manga->addChapter($chapter);
|
||||
$newChapterIds[] = $chapter->getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +144,8 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz
|
||||
$currentChapter->getTitle(),
|
||||
null, // volume = null
|
||||
$currentChapter->isVisible(),
|
||||
$currentChapter->getCbzPath(),
|
||||
$currentChapter->getPagesDirectory(),
|
||||
$currentChapter->getPageCount(),
|
||||
$currentChapter->getCreatedAt()
|
||||
);
|
||||
}
|
||||
@@ -156,7 +158,8 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz
|
||||
$currentChapter->getTitle(),
|
||||
$prevVolume, // prend le volume des adjacents
|
||||
$currentChapter->isVisible(),
|
||||
$currentChapter->getCbzPath(),
|
||||
$currentChapter->getPagesDirectory(),
|
||||
$currentChapter->getPageCount(),
|
||||
$currentChapter->getCreatedAt()
|
||||
);
|
||||
}
|
||||
@@ -210,7 +213,8 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz
|
||||
$currentChapter->getTitle(),
|
||||
$prevVolume,
|
||||
$currentChapter->isVisible(),
|
||||
$currentChapter->getCbzPath(),
|
||||
$currentChapter->getPagesDirectory(),
|
||||
$currentChapter->getPageCount(),
|
||||
$currentChapter->getCreatedAt()
|
||||
);
|
||||
}
|
||||
@@ -223,7 +227,8 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz
|
||||
$currentChapter->getTitle(),
|
||||
$nextVolume,
|
||||
$currentChapter->isVisible(),
|
||||
$currentChapter->getCbzPath(),
|
||||
$currentChapter->getPagesDirectory(),
|
||||
$currentChapter->getPageCount(),
|
||||
$currentChapter->getCreatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user