feat: ajout de la gestion des commandes pour la suppression des fichiers CBZ et des chapitres, avec création des gestionnaires et des ressources API correspondantes

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-06-29 18:33:33 +02:00
parent 7fe4ac0d3b
commit 37e1b202c2
42 changed files with 1413 additions and 21 deletions

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Domain\Manga\Application\Command;
use App\Domain\Shared\Domain\Contract\CommandInterface;
readonly class DeleteCbz implements CommandInterface
{
public function __construct(
public string $chapterId
) {}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Domain\Manga\Application\Command;
use App\Domain\Shared\Domain\Contract\CommandInterface;
readonly class DeleteChapter implements CommandInterface
{
public function __construct(
public string $chapterId
) {}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Domain\Manga\Application\CommandHandler;
use App\Domain\Manga\Application\Command\DeleteCbz;
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface;
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException;
use App\Domain\Shared\Domain\Contract\CommandHandlerInterface;
use App\Domain\Shared\Domain\Contract\CommandInterface;
readonly class DeleteCbzHandler implements CommandHandlerInterface
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository,
private FileServiceInterface $fileService
) {}
public function handle(CommandInterface $command): void
{
assert($command instanceof DeleteCbz);
$chapter = $this->chapterRepository->findVisibleById($command->chapterId);
if (!$chapter) {
throw new ChapterNotFoundException($command->chapterId);
}
// Check if chapter has a CBZ file
if (!$chapter->isAvailable()) {
throw new CbzFileNotFoundException($command->chapterId);
}
// Delete the physical CBZ file
// Note: We'll need to get the CBZ path from somewhere, likely from a legacy repository
// For now, we'll just mark the chapter as not available
// Update chapter to mark CBZ as not available
$updatedChapter = new \App\Domain\Manga\Domain\Model\Chapter(
new \App\Domain\Manga\Domain\Model\ValueObject\ChapterId($chapter->getId()),
$chapter->getMangaId(),
$chapter->getNumber(),
$chapter->getTitle(),
$chapter->getVolume(),
$chapter->isVisible(),
false, // isAvailable = false (CBZ removed)
$chapter->getCreatedAt()
);
$this->chapterRepository->save($updatedChapter);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Domain\Manga\Application\CommandHandler;
use App\Domain\Manga\Application\Command\DeleteChapter;
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
use App\Domain\Shared\Domain\Contract\CommandHandlerInterface;
use App\Domain\Shared\Domain\Contract\CommandInterface;
readonly class DeleteChapterHandler implements CommandHandlerInterface
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository
) {}
public function handle(CommandInterface $command): void
{
assert($command instanceof DeleteChapter);
$chapter = $this->chapterRepository->findVisibleById($command->chapterId);
if (!$chapter) {
throw new ChapterNotFoundException($command->chapterId);
}
// Soft delete by setting isVisible to false
$updatedChapter = new \App\Domain\Manga\Domain\Model\Chapter(
new \App\Domain\Manga\Domain\Model\ValueObject\ChapterId($chapter->getId()),
$chapter->getMangaId(),
$chapter->getNumber(),
$chapter->getTitle(),
$chapter->getVolume(),
false, // isVisible = false (soft delete)
$chapter->isAvailable(),
$chapter->getCreatedAt()
);
$this->chapterRepository->save($updatedChapter);
}
}

View File

@@ -238,7 +238,7 @@ readonly class FetchMangaChaptersHandler
title: $chapter->getTitle(),
volume: $newVolume,
isVisible: $chapter->isVisible(),
isAvailable: $chapter->isAvailable(),
cbzPath: $chapter->getCbzPath(),
createdAt: $chapter->getCreatedAt()
);
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Domain\Manga\Application\Query;
use App\Domain\Shared\Domain\Contract\QueryInterface;
readonly class DownloadCbz implements QueryInterface
{
public function __construct(
public string $chapterId
) {}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Domain\Manga\Application\Query;
use App\Domain\Shared\Domain\Contract\QueryInterface;
readonly class DownloadVolume implements QueryInterface
{
public function __construct(
public string $mangaId,
public int $volume
) {}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Domain\Manga\Application\QueryHandler;
use App\Domain\Manga\Application\Query\DownloadCbz;
use App\Domain\Manga\Application\Response\DownloadResponse;
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface;
use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException;
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
use App\Domain\Manga\Domain\Exception\ChapterNotAvailableException;
use App\Domain\Shared\Domain\Contract\QueryHandlerInterface;
use App\Domain\Shared\Domain\Contract\QueryInterface;
use App\Domain\Shared\Domain\Contract\ResponseInterface;
readonly class DownloadCbzHandler implements QueryHandlerInterface
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository,
private FileServiceInterface $fileService
) {}
public function handle(QueryInterface $query): ResponseInterface
{
assert($query instanceof DownloadCbz);
$chapter = $this->chapterRepository->findVisibleById($query->chapterId);
if (!$chapter) {
throw new ChapterNotFoundException($query->chapterId);
}
if (!$chapter->isAvailable()) {
throw new ChapterNotAvailableException($query->chapterId);
}
// Use the actual CBZ path from the chapter
$cbzPath = $chapter->getCbzPath();
// Extract the existing filename from the path
$filename = basename($cbzPath);
try {
$httpResponse = $this->fileService->downloadCbz($cbzPath, $filename);
} catch (CbzFileNotFoundException $e) {
throw new ChapterNotAvailableException($query->chapterId);
}
return new DownloadResponse($httpResponse);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Domain\Manga\Application\QueryHandler;
use App\Domain\Manga\Application\Query\DownloadVolume;
use App\Domain\Manga\Application\Response\DownloadResponse;
use App\Domain\Manga\Domain\Contract\Repository\ChapterRepositoryInterface;
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface;
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
use App\Domain\Manga\Domain\Exception\VolumeNotFoundException;
use App\Domain\Shared\Domain\Contract\QueryHandlerInterface;
use App\Domain\Shared\Domain\Contract\QueryInterface;
use App\Domain\Shared\Domain\Contract\ResponseInterface;
readonly class DownloadVolumeHandler implements QueryHandlerInterface
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository,
private MangaRepositoryInterface $mangaRepository,
private FileServiceInterface $fileService
) {}
public function handle(QueryInterface $query): ResponseInterface
{
assert($query instanceof DownloadVolume);
$manga = $this->mangaRepository->findById($query->mangaId);
if (!$manga) {
throw new MangaNotFoundException($query->mangaId);
}
$chapters = $this->chapterRepository->findVisibleWithCbzByMangaIdAndVolume(
$query->mangaId,
$query->volume
);
if (empty($chapters)) {
throw new VolumeNotFoundException($query->mangaId, $query->volume);
}
// Collect CBZ paths for all chapters
$cbzPaths = [];
foreach ($chapters as $chapter) {
$cbzPaths[] = $chapter->getCbzPath();
}
$volumeName = sprintf('%s-volume-%d',
$manga->getSlug()->getValue(),
$query->volume
);
$httpResponse = $this->fileService->createVolumeCbz($cbzPaths, $volumeName);
return new DownloadResponse($httpResponse);
}
}

View File

@@ -38,7 +38,7 @@ readonly class GetMangaChaptersHandler
title: $chapter->getTitle(),
volume: $chapter->getVolume(),
isVisible: $chapter->isVisible(),
isAvailable: $chapter->isAvailable(),
cbzPath: $chapter->getCbzPath(),
createdAt: $chapter->getCreatedAt()
),
$chapters
@@ -48,4 +48,4 @@ readonly class GetMangaChaptersHandler
limit: $query->limit
);
}
}
}

View File

@@ -10,7 +10,7 @@ readonly class ChapterResponse
public ?string $title,
public ?int $volume,
public bool $isVisible,
public bool $isAvailable,
public ?string $cbzPath,
public \DateTimeImmutable $createdAt
) {}
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Domain\Manga\Application\Response;
use App\Domain\Shared\Domain\Contract\ResponseInterface;
use Symfony\Component\HttpFoundation\Response;
readonly class DownloadResponse implements ResponseInterface
{
public function __construct(
public Response $httpResponse
) {}
}