feat: migrer vers Symfony 8, PHP 8.4 et les dépendances majeures associées
- PHP 8.3 → 8.4 (Dockerfile + composer.json) - Symfony 7.0 → 8.0 (tous les composants symfony/*) - API Platform 3.x → 4.x : migration openapiContext → openapi: new Operation(...) - Doctrine DBAL 3 → 4 : suppression use_savepoints, replace prepare/executeQuery - Doctrine ORM 2.x → 3.x : ClassMetadataInfo → ClassMetadata, setParameters → setParameter - Doctrine Bundle 2.x → 3.x, Fixtures Bundle 3.x → 4.x - zenstruck/foundry 1.x → 2.x : ModelFactory → PersistentObjectFactory, getDefaults → defaults - phpmd/phpmd 2.x → 3.x-dev (seule version supportant Symfony 8) - phparkitect 0.3 → 0.8 : NotDependsOnTheseNamespaces prend un array - symfony/mercure-bundle 0.3 → 0.4, symfony/monolog-bundle 3 → 4 - Suppression de runtime/frankenphp-symfony (intégré nativement dans symfony/runtime 8) - worker.Caddyfile : suppression de APP_RUNTIME (détection automatique Symfony 8) - Routes errors.xml/wdt.xml/profiler.xml → .php (Symfony 8 supprime le XML) - Types::ARRAY → Types::JSON dans Entity/Manga.php (DBAL 4 retire array type) - Suppression de src/Schedule.php (doublon vide avec MonitoringSchedule) - Tests : hydra:Collection → Collection, hydra:member → member (API Platform 4)
This commit is contained in:
parent
5a0888eb28
commit
5ed303612a
@@ -7,7 +7,7 @@ readonly class ChapterEditData
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public ?string $title = null,
|
||||
public ?int $volume = null
|
||||
public ?int $volume = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace App\Domain\Manga\Application\Command;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class CheckMonitoredMangas
|
||||
{
|
||||
public function __construct(
|
||||
public ?DateTimeImmutable $since = null
|
||||
public ?\DateTimeImmutable $since = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class CreateManga
|
||||
public string $status,
|
||||
public ?string $externalId,
|
||||
public ?string $imageUrl,
|
||||
public ?float $rating
|
||||
public ?float $rating,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Command;
|
||||
readonly class CreateMangaFromMangadex
|
||||
{
|
||||
public function __construct(
|
||||
public string $externalId
|
||||
public string $externalId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteCbz implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $chapterId
|
||||
public string $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteChapter implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $chapterId
|
||||
public string $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteManga implements CommandInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId
|
||||
public string $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class EditManga
|
||||
public ?array $genres = null,
|
||||
public ?string $status = null,
|
||||
public ?float $rating = null,
|
||||
public ?array $alternativeSlugs = null
|
||||
public ?array $alternativeSlugs = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class EditMultipleChapters
|
||||
* @param array<ChapterEditData> $chapters
|
||||
*/
|
||||
public function __construct(
|
||||
public array $chapters
|
||||
public array $chapters,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
readonly class FetchMangaChapters
|
||||
{
|
||||
public function __construct(
|
||||
public MangaId $mangaId
|
||||
public MangaId $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class ImportChapter
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public float $chapterNumber,
|
||||
public string $fileBinary
|
||||
public string $fileBinary,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class ImportVolume
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public int $volumeNumber,
|
||||
public string $fileBinary
|
||||
public string $fileBinary,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
readonly class RefreshMangaChapters
|
||||
{
|
||||
public function __construct(
|
||||
public MangaId $mangaId
|
||||
public MangaId $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class ToggleMangaMonitoring
|
||||
{
|
||||
public function __construct(
|
||||
public MangaId $mangaId,
|
||||
public bool $enabled
|
||||
public bool $enabled,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ use App\Domain\Manga\Application\Command\CheckMonitoredMangas;
|
||||
use App\Domain\Manga\Application\Command\RefreshMangaChapters;
|
||||
use App\Domain\Manga\Application\Query\MonitoringCriteria;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
readonly class CheckMonitoredMangasHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private MessageBusInterface $commandBus
|
||||
private MessageBusInterface $commandBus,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -21,7 +20,7 @@ readonly class CheckMonitoredMangasHandler
|
||||
{
|
||||
$criteria = new MonitoringCriteria(
|
||||
enabled: true,
|
||||
lastCheckBefore: $command->since ?? new DateTimeImmutable('-1 hour')
|
||||
lastCheckBefore: $command->since ?? new \DateTimeImmutable('-1 hour')
|
||||
);
|
||||
|
||||
$monitoredMangas = $this->mangaRepository->findByMonitoringCriteria($criteria);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\CreateMangaFromMangadex;
|
||||
use App\Domain\Manga\Application\Response\CreateMangaResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Service\ImageProcessorInterface;
|
||||
@@ -19,7 +18,7 @@ readonly class CreateMangaFromMangadexHandler
|
||||
private MangaProviderInterface $mangaProvider,
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageProcessorInterface $imageProcessor,
|
||||
private EventDispatcherInterface $eventDispatcher
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -27,7 +26,7 @@ readonly class CreateMangaFromMangadexHandler
|
||||
{
|
||||
$manga = $this->mangaProvider->findByExternalId(new ExternalId($command->externalId));
|
||||
|
||||
if ($manga === null) {
|
||||
if (null === $manga) {
|
||||
throw new MangaNotFoundException('Manga not found on Mangadex');
|
||||
}
|
||||
|
||||
@@ -41,7 +40,7 @@ readonly class CreateMangaFromMangadexHandler
|
||||
// Met à jour le manga avec les nouveaux chemins d'images
|
||||
$manga->updateImageUrls(new ImageUrls($fullImagePath, $thumbnailPath));
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Erreur lors du traitement de l\'image : ' . $e->getMessage());
|
||||
throw new \RuntimeException('Erreur lors du traitement de l\'image : '.$e->getMessage());
|
||||
}
|
||||
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
@@ -20,7 +20,7 @@ readonly class CreateMangaHandler
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageProcessorInterface $imageProcessor,
|
||||
private MessageBusInterface $messageBus
|
||||
private MessageBusInterface $messageBus,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ readonly class CreateMangaHandler
|
||||
$thumbnailPath = $this->imageProcessor->createThumbnail($fullImagePath);
|
||||
$manga->updateImageUrls(new ImageUrls($fullImagePath, $thumbnailPath));
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Erreur lors du traitement de l\'image : ' . $e->getMessage());
|
||||
throw new \RuntimeException('Erreur lors du traitement de l\'image : '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace App\Domain\Manga\Application\CommandHandler;
|
||||
use App\Domain\Manga\Application\Command\DeleteCbz;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
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\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Shared\Domain\Contract\CommandHandlerInterface;
|
||||
use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class DeleteCbzHandler implements CommandHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private FileServiceInterface $fileService
|
||||
private FileServiceInterface $fileService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteChapterHandler implements CommandHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface;
|
||||
readonly class DeleteMangaHandler implements CommandHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
readonly class EditMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -23,35 +23,35 @@ readonly class EditMangaHandler
|
||||
}
|
||||
|
||||
// Update only provided fields (partial update)
|
||||
if ($command->title !== null) {
|
||||
if (null !== $command->title) {
|
||||
$manga->updateTitle(new MangaTitle($command->title));
|
||||
}
|
||||
|
||||
if ($command->description !== null) {
|
||||
if (null !== $command->description) {
|
||||
$manga->updateDescription($command->description);
|
||||
}
|
||||
|
||||
if ($command->author !== null) {
|
||||
if (null !== $command->author) {
|
||||
$manga->updateAuthor($command->author);
|
||||
}
|
||||
|
||||
if ($command->publicationYear !== null) {
|
||||
if (null !== $command->publicationYear) {
|
||||
$manga->updatePublicationYear($command->publicationYear);
|
||||
}
|
||||
|
||||
if ($command->genres !== null) {
|
||||
if (null !== $command->genres) {
|
||||
$manga->updateGenres($command->genres);
|
||||
}
|
||||
|
||||
if ($command->status !== null) {
|
||||
if (null !== $command->status) {
|
||||
$manga->updateStatus($command->status);
|
||||
}
|
||||
|
||||
if ($command->rating !== null) {
|
||||
if (null !== $command->rating) {
|
||||
$manga->setRating($command->rating);
|
||||
}
|
||||
|
||||
if ($command->alternativeSlugs !== null) {
|
||||
if (null !== $command->alternativeSlugs) {
|
||||
$manga->updateAlternativeSlugs($command->alternativeSlugs);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
readonly class EditMultipleChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ readonly class EditMultipleChaptersHandler
|
||||
|
||||
$manga = $this->mangaRepository->findById($chapter->getMangaId()->getValue());
|
||||
|
||||
if ($chapterData->title !== null) {
|
||||
if (null !== $chapterData->title) {
|
||||
$manga->updateChapterTitle($chapter, $chapterData->title);
|
||||
}
|
||||
|
||||
if ($chapterData->volume !== null) {
|
||||
if (null !== $chapterData->volume) {
|
||||
$manga->updateChapterVolume($chapter, $chapterData->volume);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ readonly class FetchMangaChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ChapterSynchronizationServiceInterface $chapterSynchronizationService
|
||||
private ChapterSynchronizationServiceInterface $chapterSynchronizationService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ readonly class FetchMangaChaptersHandler
|
||||
{
|
||||
$manga = $this->mangaRepository->findById($command->mangaId->getValue());
|
||||
|
||||
if ($manga === null) {
|
||||
if (null === $manga) {
|
||||
throw new MangaNotFoundException();
|
||||
}
|
||||
|
||||
if ($manga->getExternalId() === null) {
|
||||
throw new MangadexApiException("Manga has no external_id");
|
||||
if (null === $manga->getExternalId()) {
|
||||
throw new MangadexApiException('Manga has no external_id');
|
||||
}
|
||||
|
||||
// Synchronisation initiale (pas d'événements)
|
||||
|
||||
@@ -4,15 +4,15 @@ namespace App\Domain\Manga\Application\CommandHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Command\ImportChapter;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Shared\Domain\Contract\ImageStorageInterface;
|
||||
|
||||
readonly class ImportChapterHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageStorageInterface $imageStorage
|
||||
private ImageStorageInterface $imageStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@ readonly class ImportChapterHandler
|
||||
{
|
||||
$zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04
|
||||
|
||||
return strpos($fileBinary, $zipMagicNumber) === 0;
|
||||
return 0 === strpos($fileBinary, $zipMagicNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ readonly class ImportVolumeHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ImageStorageInterface $imageStorage
|
||||
private ImageStorageInterface $imageStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -35,9 +35,7 @@ readonly class ImportVolumeHandler
|
||||
);
|
||||
|
||||
if (empty($chapters)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}"
|
||||
);
|
||||
throw new \InvalidArgumentException("No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}");
|
||||
}
|
||||
|
||||
// 4. Extract CBZ into individual images storage (shared directory for all volume chapters)
|
||||
@@ -56,6 +54,6 @@ readonly class ImportVolumeHandler
|
||||
{
|
||||
$zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04
|
||||
|
||||
return strpos($fileBinary, $zipMagicNumber) === 0;
|
||||
return 0 === strpos($fileBinary, $zipMagicNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Service\ChapterSynchronizationServiceInterface;
|
||||
use App\Domain\Manga\Domain\Event\ChapterReadyForScraping;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
readonly class RefreshMangaChaptersHandler
|
||||
@@ -15,7 +14,7 @@ readonly class RefreshMangaChaptersHandler
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private ChapterSynchronizationServiceInterface $chapterSynchronizationService,
|
||||
private MessageBusInterface $eventBus
|
||||
private MessageBusInterface $eventBus,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -23,7 +22,7 @@ readonly class RefreshMangaChaptersHandler
|
||||
{
|
||||
$manga = $this->mangaRepository->findById($command->mangaId->getValue());
|
||||
|
||||
if ($manga === null) {
|
||||
if (null === $manga) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
|
||||
@@ -31,7 +30,7 @@ readonly class RefreshMangaChaptersHandler
|
||||
$newChapterIds = $this->chapterSynchronizationService->synchronizeChapters($manga);
|
||||
|
||||
// Mise à jour de la date de monitoring
|
||||
$manga->updateLastMonitoringCheck(new DateTimeImmutable());
|
||||
$manga->updateLastMonitoringCheck(new \DateTimeImmutable());
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
// Événement de scraping pour chaque nouveau chapitre
|
||||
|
||||
@@ -9,7 +9,7 @@ use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
readonly class ToggleMangaMonitoringHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
readonly class MangaCreatedEventListener
|
||||
{
|
||||
public function __construct(
|
||||
private FetchMangaChaptersHandler $fetchMangaChaptersHandler
|
||||
private FetchMangaChaptersHandler $fetchMangaChaptersHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ readonly class VolumeImportedEventListener
|
||||
}
|
||||
|
||||
$chapters = $this->mangaRepository->findChaptersByMangaIdAndVolume($manga->getId()->getValue(), (int) $event->volume);
|
||||
if ($chapters === []) {
|
||||
if ([] === $chapters) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\QueryInterface;
|
||||
readonly class DownloadCbz implements QueryInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $chapterId
|
||||
public string $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class DownloadVolume implements QueryInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public int $volume
|
||||
public int $volume,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class FindMangaMatchByFilename
|
||||
{
|
||||
public function __construct(
|
||||
public string $filename
|
||||
public string $filename,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class GetMangaById
|
||||
{
|
||||
public function __construct(
|
||||
public string $id
|
||||
public string $id,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class GetMangaBySlug
|
||||
{
|
||||
public function __construct(
|
||||
public string $slug
|
||||
public string $slug,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class GetMangaChapters
|
||||
public string $mangaId,
|
||||
public ?int $page = 1,
|
||||
public ?int $limit = 20,
|
||||
public ?string $sortOrder = 'desc'
|
||||
public ?string $sortOrder = 'desc',
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class GetMangaList
|
||||
public ?int $page = 1,
|
||||
public ?int $limit = 20,
|
||||
public ?string $sortBy = 'title',
|
||||
public ?string $sortOrder = 'asc'
|
||||
public ?string $sortOrder = 'asc',
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
namespace App\Domain\Manga\Application\Query;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class MonitoringCriteria
|
||||
{
|
||||
public function __construct(
|
||||
public bool $enabled,
|
||||
public ?DateTimeImmutable $lastCheckBefore = null
|
||||
public ?\DateTimeImmutable $lastCheckBefore = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ readonly class SearchLocalManga
|
||||
public function __construct(
|
||||
public string $query,
|
||||
public int $page = 1,
|
||||
public int $limit = 20
|
||||
public int $limit = 20,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query;
|
||||
readonly class SearchManga
|
||||
{
|
||||
public function __construct(
|
||||
public string $title
|
||||
public string $title,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class DiscoverMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private MangaProviderInterface $mangaProvider
|
||||
private MangaProviderInterface $mangaProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ readonly class DiscoverMangaHandler
|
||||
|
||||
$recommendations = array_values(array_filter(
|
||||
$collection->getItems(),
|
||||
fn (Manga $m) => $m->getExternalId() === null
|
||||
fn (Manga $m) => null === $m->getExternalId()
|
||||
|| !in_array($m->getExternalId()->getValue(), $ownedExternalIds, true)
|
||||
));
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use App\Domain\Manga\Application\Response\DownloadResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
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\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Shared\Domain\Contract\QueryHandlerInterface;
|
||||
use App\Domain\Shared\Domain\Contract\QueryInterface;
|
||||
use App\Domain\Shared\Domain\Contract\ResponseInterface;
|
||||
@@ -17,7 +17,7 @@ readonly class DownloadCbzHandler implements QueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private FileServiceInterface $fileService
|
||||
private FileServiceInterface $fileService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ readonly class DownloadVolumeHandler implements QueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private FileServiceInterface $fileService
|
||||
private FileServiceInterface $fileService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ readonly class FindMangaMatchByFilenameHandler
|
||||
{
|
||||
public function __construct(
|
||||
private FilenameAnalyzerInterface $filenameAnalyzer,
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ readonly class FindMangaMatchByFilenameHandler
|
||||
|
||||
/**
|
||||
* Calcule un score de correspondance entre le manga et le titre recherché
|
||||
* Score plus élevé = meilleure correspondance
|
||||
* Score plus élevé = meilleure correspondance.
|
||||
*/
|
||||
private function calculateMatchScore(Manga $manga, string $searchedTitle): int
|
||||
{
|
||||
@@ -97,12 +97,12 @@ readonly class FindMangaMatchByFilenameHandler
|
||||
}
|
||||
|
||||
// Le titre du manga contient le terme recherché
|
||||
if (stripos($mangaTitle, $searchedTitle) !== false) {
|
||||
if (false !== stripos($mangaTitle, $searchedTitle)) {
|
||||
$score += 50;
|
||||
}
|
||||
|
||||
// Le terme recherché contient le titre du manga
|
||||
if (stripos($searchedTitle, $mangaTitle) !== false) {
|
||||
if (false !== stripos($searchedTitle, $mangaTitle)) {
|
||||
$score += 40;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
readonly class GetMangaByIdHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
readonly class GetMangaBySlugHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use App\Domain\Manga\Domain\Model\Chapter;
|
||||
readonly class GetMangaChaptersHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ readonly class GetMangaChaptersHandler
|
||||
|
||||
$grouped = $this->groupChapters($allChapters);
|
||||
|
||||
if ($query->sortOrder === 'desc') {
|
||||
if ('desc' === $query->sortOrder) {
|
||||
usort($grouped, fn (ChapterResponse $a, ChapterResponse $b) => $b->number <=> $a->number);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ readonly class GetMangaChaptersHandler
|
||||
$pagesDir = $chapter->getPagesDirectory();
|
||||
$volume = $chapter->getVolume();
|
||||
|
||||
if ($pagesDir !== null && $volume !== null) {
|
||||
if (null !== $pagesDir && null !== $volume) {
|
||||
if ($pagesDir === $currentPagesDir && $volume === $currentVolume) {
|
||||
$currentGroup[] = $chapter;
|
||||
} else {
|
||||
@@ -104,7 +104,7 @@ readonly class GetMangaChaptersHandler
|
||||
$max = max($numbers);
|
||||
|
||||
$fmt = fn (float $n) => $n == (int) $n ? (string) (int) $n : (string) $n;
|
||||
$range = count($group) > 1 ? $fmt($min) . '-' . $fmt($max) : $fmt($min);
|
||||
$range = count($group) > 1 ? $fmt($min).'-'.$fmt($max) : $fmt($min);
|
||||
|
||||
return new ChapterResponse(
|
||||
id: $first->getId(),
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
namespace App\Domain\Manga\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Query\GetMangaList;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Application\Response\MangaListResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
|
||||
readonly class GetMangaListHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ readonly class GetMangaListHandler
|
||||
foreach ($mangas as $manga) {
|
||||
$id = $manga->getId()->getValue();
|
||||
$chapterCounts[$id] = [
|
||||
'total' => $this->mangaRepository->countChapters($id),
|
||||
'total' => $this->mangaRepository->countChapters($id),
|
||||
'scraped' => $this->mangaRepository->countAvailableChapters($id),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\Manga;
|
||||
readonly class SearchLocalMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $repository
|
||||
private MangaRepositoryInterface $repository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\Manga;
|
||||
readonly class SearchMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaProviderInterface $mangaProvider
|
||||
private MangaProviderInterface $mangaProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ readonly class SearchMangaHandler
|
||||
{
|
||||
$mangaCollection = $this->mangaProvider->search($query->title);
|
||||
|
||||
|
||||
return new MangaSearchResponse(
|
||||
array_map(
|
||||
fn (Manga $manga, int $index) => new MangaSearchItem(
|
||||
|
||||
@@ -8,7 +8,7 @@ readonly class ChapterListResponse
|
||||
public array $chapters,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit
|
||||
public int $limit,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
readonly class DownloadResponse implements ResponseInterface
|
||||
{
|
||||
public function __construct(
|
||||
public Response $httpResponse
|
||||
public Response $httpResponse,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ readonly class MangaListResponse
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit,
|
||||
public array $chapterCounts = []
|
||||
public array $chapterCounts = [],
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class MangaMatchItem
|
||||
public ?string $thumbnailUrl,
|
||||
public int $matchScore,
|
||||
public ?float $chapterNumber = null,
|
||||
public ?float $volumeNumber = null
|
||||
public ?float $volumeNumber = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class MangaMatchResponse
|
||||
public array $matches,
|
||||
public ?float $chapterNumber,
|
||||
public ?float $volumeNumber,
|
||||
public array $possibleTitles
|
||||
public array $possibleTitles,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ readonly class MangaResponse
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating,
|
||||
public bool $monitored
|
||||
public bool $monitored,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ readonly class MangaSearchItem
|
||||
public string $status,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating
|
||||
public ?float $rating,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ readonly class SearchLocalMangaResponse
|
||||
public array $items,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit
|
||||
public int $limit,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ use App\Domain\Manga\Domain\Exception\MangadexAuthenticationException;
|
||||
interface MangadexClientInterface
|
||||
{
|
||||
/**
|
||||
* @throws \App\Domain\Manga\Domain\Exception\MangadexAuthenticationException
|
||||
* @throws MangadexAuthenticationException
|
||||
*/
|
||||
public function authenticate(): void;
|
||||
|
||||
/**
|
||||
* @throws \App\Domain\Manga\Domain\Exception\MangadexAuthenticationException
|
||||
* @throws MangadexAuthenticationException
|
||||
*/
|
||||
public function refreshToken(): void;
|
||||
|
||||
@@ -39,6 +39,7 @@ interface MangadexClientInterface
|
||||
|
||||
/**
|
||||
* @param string[] $mangaIds
|
||||
*
|
||||
* @return array{
|
||||
* statistics: array<string, array{
|
||||
* rating: array{average: float}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Domain\Manga\Domain\Contract\Repository;
|
||||
|
||||
use App\Domain\Manga\Application\Query\MonitoringCriteria;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\Chapter;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
|
||||
@@ -13,13 +13,21 @@ interface MangaRepositoryInterface
|
||||
// --- Manga ---
|
||||
|
||||
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array;
|
||||
|
||||
public function count(): int;
|
||||
|
||||
public function findById(string $id): ?Manga;
|
||||
|
||||
public function findBySlug(MangaSlug $slug): ?Manga;
|
||||
|
||||
public function findByExternalId(ExternalId $externalId): ?Manga;
|
||||
|
||||
public function save(Manga $manga): void;
|
||||
|
||||
public function delete(Manga $manga): void;
|
||||
|
||||
public function search(string $query, int $page = 1, int $limit = 20): array;
|
||||
|
||||
public function countSearch(string $query): int;
|
||||
|
||||
/**
|
||||
@@ -35,14 +43,20 @@ interface MangaRepositoryInterface
|
||||
* @return Chapter[]
|
||||
*/
|
||||
public function findAllChapters(string $mangaId, string $sortOrder = 'desc'): array;
|
||||
|
||||
public function countChapters(string $mangaId): int;
|
||||
|
||||
public function countAvailableChapters(string $mangaId): int;
|
||||
|
||||
public function findChapterById(string $id): ?Chapter;
|
||||
|
||||
public function findVisibleChapterById(string $id): ?Chapter;
|
||||
|
||||
public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter;
|
||||
|
||||
/**
|
||||
* @param float[] $chapterNumbers
|
||||
*
|
||||
* @return array<float, Chapter>
|
||||
*/
|
||||
public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array;
|
||||
@@ -61,5 +75,4 @@ interface MangaRepositoryInterface
|
||||
* @return Chapter[]
|
||||
*/
|
||||
public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use App\Domain\Manga\Domain\Model\Manga;
|
||||
interface ChapterSynchronizationServiceInterface
|
||||
{
|
||||
/**
|
||||
* Synchronise les chapitres d'un manga depuis la source externe
|
||||
* Synchronise les chapitres d'un manga depuis la source externe.
|
||||
*
|
||||
* @return string[] IDs des nouveaux chapitres ajoutés
|
||||
*/
|
||||
public function synchronizeChapters(Manga $manga): array;
|
||||
|
||||
@@ -7,23 +7,24 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
interface FileServiceInterface
|
||||
{
|
||||
/**
|
||||
* Télécharge un fichier CBZ
|
||||
* Télécharge un fichier CBZ.
|
||||
*/
|
||||
public function downloadCbz(string $filePath, string $filename): Response;
|
||||
|
||||
/**
|
||||
* Crée un fichier ZIP contenant plusieurs CBZ
|
||||
* Crée un fichier ZIP contenant plusieurs CBZ.
|
||||
*
|
||||
* @param array<string> $cbzPaths
|
||||
*/
|
||||
public function createVolumeCbz(array $cbzPaths, string $volumeName): Response;
|
||||
|
||||
/**
|
||||
* Supprime un fichier CBZ du système de fichiers
|
||||
* Supprime un fichier CBZ du système de fichiers.
|
||||
*/
|
||||
public function deleteCbzFile(string $filePath): bool;
|
||||
|
||||
/**
|
||||
* Vérifie si un fichier CBZ existe
|
||||
* Vérifie si un fichier CBZ existe.
|
||||
*/
|
||||
public function cbzExists(string $filePath): bool;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
|
||||
readonly class ChapterReadyForScraping
|
||||
{
|
||||
public function __construct(
|
||||
public ChapterId $chapterId
|
||||
public ChapterId $chapterId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ readonly class MangaCreated
|
||||
{
|
||||
public function __construct(
|
||||
public string $mangaId,
|
||||
public string $externalId
|
||||
public string $externalId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class CbzFileNotFoundException extends DomainException
|
||||
class CbzFileNotFoundException extends \DomainException
|
||||
{
|
||||
public function __construct(string $filePath)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class ChapterNotAvailableException extends DomainException
|
||||
class ChapterNotAvailableException extends \DomainException
|
||||
{
|
||||
public function __construct(int $chapterId)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class ChapterNotFoundException extends DomainException
|
||||
class ChapterNotFoundException extends \DomainException
|
||||
{
|
||||
public function __construct(string $chapterId)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Domain\Manga\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
class VolumeNotFoundException extends DomainException
|
||||
class VolumeNotFoundException extends \DomainException
|
||||
{
|
||||
public function __construct(string $mangaId, int $volume)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ readonly class AnalyzedFilename
|
||||
public function __construct(
|
||||
private MangaTitle $title,
|
||||
private ?ChapterNumber $chapterNumber = null,
|
||||
private ?VolumeNumber $volumeNumber = null
|
||||
private ?VolumeNumber $volumeNumber = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ readonly class AnalyzedFilename
|
||||
|
||||
public function hasChapterNumber(): bool
|
||||
{
|
||||
return $this->chapterNumber !== null;
|
||||
return null !== $this->chapterNumber;
|
||||
}
|
||||
|
||||
public function hasVolumeNumber(): bool
|
||||
{
|
||||
return $this->volumeNumber !== null;
|
||||
return null !== $this->volumeNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class Chapter
|
||||
private bool $isVisible,
|
||||
private ?string $pagesDirectory = null,
|
||||
private int $pageCount = 0,
|
||||
private \DateTimeImmutable $createdAt = new \DateTimeImmutable()
|
||||
private \DateTimeImmutable $createdAt = new \DateTimeImmutable(),
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class Chapter
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return $this->pagesDirectory !== null;
|
||||
return null !== $this->pagesDirectory;
|
||||
}
|
||||
|
||||
public function getPagesDirectory(): ?string
|
||||
|
||||
@@ -9,7 +9,6 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MonitoringStatus;
|
||||
use App\Domain\Shared\Domain\Model\AggregateRoot;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final class Manga extends AggregateRoot
|
||||
{
|
||||
@@ -23,22 +22,22 @@ final class Manga extends AggregateRoot
|
||||
private array $chaptersToDelete = [];
|
||||
|
||||
public function __construct(
|
||||
private MangaId $id,
|
||||
private MangaTitle $title,
|
||||
private MangaSlug $slug,
|
||||
private string $description,
|
||||
private string $author,
|
||||
private int $publicationYear,
|
||||
private array $genres,
|
||||
private string $status,
|
||||
private MangaId $id,
|
||||
private MangaTitle $title,
|
||||
private MangaSlug $slug,
|
||||
private string $description,
|
||||
private string $author,
|
||||
private int $publicationYear,
|
||||
private array $genres,
|
||||
private string $status,
|
||||
private ?ExternalId $externalId = null,
|
||||
private ?string $imageUrl = null,
|
||||
private ?float $rating = null,
|
||||
private ?ImageUrls $imageUrls = null,
|
||||
private array $alternativeSlugs = [],
|
||||
private ?DateTimeImmutable $createdAt = null,
|
||||
private ?string $imageUrl = null,
|
||||
private ?float $rating = null,
|
||||
private ?ImageUrls $imageUrls = null,
|
||||
private array $alternativeSlugs = [],
|
||||
private ?\DateTimeImmutable $createdAt = null,
|
||||
private ?MonitoringStatus $monitoringStatus = null,
|
||||
private ?DateTimeImmutable $lastMonitoringCheck = null,
|
||||
private ?\DateTimeImmutable $lastMonitoringCheck = null,
|
||||
) {
|
||||
$this->monitoringStatus = $this->monitoringStatus ?? MonitoringStatus::disabled();
|
||||
}
|
||||
@@ -158,7 +157,7 @@ final class Manga extends AggregateRoot
|
||||
$this->alternativeSlugs = $alternativeSlugs;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeImmutable
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
@@ -181,7 +180,7 @@ final class Manga extends AggregateRoot
|
||||
public function enableMonitoring(): void
|
||||
{
|
||||
$this->monitoringStatus = MonitoringStatus::enabled();
|
||||
$this->lastMonitoringCheck = new DateTimeImmutable();
|
||||
$this->lastMonitoringCheck = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function disableMonitoring(): void
|
||||
@@ -190,12 +189,12 @@ final class Manga extends AggregateRoot
|
||||
$this->lastMonitoringCheck = null;
|
||||
}
|
||||
|
||||
public function getLastMonitoringCheck(): ?DateTimeImmutable
|
||||
public function getLastMonitoringCheck(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->lastMonitoringCheck;
|
||||
}
|
||||
|
||||
public function updateLastMonitoringCheck(DateTimeImmutable $lastMonitoringCheck): void
|
||||
public function updateLastMonitoringCheck(\DateTimeImmutable $lastMonitoringCheck): void
|
||||
{
|
||||
$this->lastMonitoringCheck = $lastMonitoringCheck;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||
readonly class ChapterId
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
readonly class ChapterNumber
|
||||
{
|
||||
public function __construct(
|
||||
private float $value
|
||||
private float $value,
|
||||
) {
|
||||
if ($value < 0) {
|
||||
throw new InvalidArgumentException('Chapter number cannot be negative');
|
||||
throw new \InvalidArgumentException('Chapter number cannot be negative');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidExternalIdException;
|
||||
readonly class ExternalId
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidExternalIdException('External ID cannot be empty');
|
||||
|
||||
@@ -6,7 +6,7 @@ readonly class ImageUrls
|
||||
{
|
||||
public function __construct(
|
||||
private string $full,
|
||||
private string $thumbnail
|
||||
private string $thumbnail,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidMangaIdException;
|
||||
readonly class MangaId
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidMangaIdException('Manga ID cannot be empty');
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidMangaSlugException;
|
||||
readonly class MangaSlug
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
if (empty(trim($value))) {
|
||||
throw new InvalidMangaSlugException('Manga slug cannot be empty');
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidMangaTitleException;
|
||||
readonly class MangaTitle
|
||||
{
|
||||
public function __construct(
|
||||
private string $value
|
||||
private string $value,
|
||||
) {
|
||||
if (empty(trim($value))) {
|
||||
throw new InvalidMangaTitleException('Manga title cannot be empty');
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||
readonly class MonitoringStatus
|
||||
{
|
||||
public function __construct(
|
||||
private bool $enabled
|
||||
private bool $enabled,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Manga\Domain\Model\ValueObject;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
readonly class VolumeNumber
|
||||
{
|
||||
public function __construct(
|
||||
private float $value
|
||||
private float $value,
|
||||
) {
|
||||
if ($value < 0) {
|
||||
throw new InvalidArgumentException('Volume number cannot be negative');
|
||||
throw new \InvalidArgumentException('Volume number cannot be negative');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,16 @@ use App\Domain\Manga\Application\Command\ImportChapter;
|
||||
use App\Domain\Manga\Application\CommandHandler\ImportChapterHandler;
|
||||
use App\Domain\Manga\Domain\Exception\ChapterNotFoundException;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\ImportChapterResource;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
#[AsController]
|
||||
final class ImportChapterController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImportChapterHandler $commandHandler
|
||||
private readonly ImportChapterHandler $commandHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -31,19 +29,19 @@ final class ImportChapterController extends AbstractController
|
||||
// Validate required fields
|
||||
if (!$mangaId) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'mangaId', 'message' => 'mangaId is required']
|
||||
['propertyPath' => 'mangaId', 'message' => 'mangaId is required'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$chapterNumber) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'chapterNumber', 'message' => 'chapterNumber is required']
|
||||
['propertyPath' => 'chapterNumber', 'message' => 'chapterNumber is required'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$uploadedFile) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file']
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
@@ -56,9 +54,9 @@ final class ImportChapterController extends AbstractController
|
||||
try {
|
||||
// Read file binary content
|
||||
$fileBinary = file_get_contents($uploadedFile->getPathname());
|
||||
if ($fileBinary === false) {
|
||||
if (false === $fileBinary) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file']
|
||||
['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file'],
|
||||
], 400);
|
||||
}
|
||||
|
||||
@@ -75,31 +73,27 @@ final class ImportChapterController extends AbstractController
|
||||
return $this->json([
|
||||
'message' => 'Chapter imported successfully',
|
||||
'mangaId' => $mangaId,
|
||||
'chapterNumber' => $chapterNumber
|
||||
'chapterNumber' => $chapterNumber,
|
||||
], 200);
|
||||
|
||||
} catch (MangaNotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Manga not found',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], 404);
|
||||
|
||||
} catch (ChapterNotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Chapter not found',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], 404);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Invalid file',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], 400);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'error' => 'Import failed',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
@@ -112,8 +106,9 @@ final class ImportChapterController extends AbstractController
|
||||
if (!$uploadedFile->isValid()) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage()
|
||||
'message' => 'The uploaded file is not valid: '.$uploadedFile->getErrorMessage(),
|
||||
];
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
@@ -122,7 +117,7 @@ final class ImportChapterController extends AbstractController
|
||||
if ($uploadedFile->getSize() > $maxSize) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is too large. Allowed size is 500MB.'
|
||||
'message' => 'The uploaded file is too large. Allowed size is 500MB.',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -133,7 +128,7 @@ final class ImportChapterController extends AbstractController
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'Please upload a valid CBZ file'
|
||||
'message' => 'Please upload a valid CBZ file',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Controller;
|
||||
use App\Domain\Manga\Application\Command\ImportVolume;
|
||||
use App\Domain\Manga\Application\CommandHandler\ImportVolumeHandler;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\ImportVolumeResource;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -15,7 +14,7 @@ use Symfony\Component\HttpKernel\Attribute\AsController;
|
||||
final class ImportVolumeController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImportVolumeHandler $commandHandler
|
||||
private readonly ImportVolumeHandler $commandHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -29,19 +28,19 @@ final class ImportVolumeController extends AbstractController
|
||||
// Validate required fields
|
||||
if (!$mangaId) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'mangaId', 'message' => 'mangaId is required']
|
||||
['propertyPath' => 'mangaId', 'message' => 'mangaId is required'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$volumeNumber) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'volumeNumber', 'message' => 'volumeNumber is required']
|
||||
['propertyPath' => 'volumeNumber', 'message' => 'volumeNumber is required'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!$uploadedFile) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file']
|
||||
['propertyPath' => 'file', 'message' => 'Please upload a file'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
@@ -54,9 +53,9 @@ final class ImportVolumeController extends AbstractController
|
||||
try {
|
||||
// Read file binary content
|
||||
$fileBinary = file_get_contents($uploadedFile->getPathname());
|
||||
if ($fileBinary === false) {
|
||||
if (false === $fileBinary) {
|
||||
return $this->json([
|
||||
['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file']
|
||||
['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file'],
|
||||
], 400);
|
||||
}
|
||||
|
||||
@@ -73,26 +72,24 @@ final class ImportVolumeController extends AbstractController
|
||||
return $this->json([
|
||||
'message' => 'Volume imported successfully',
|
||||
'mangaId' => $mangaId,
|
||||
'volumeNumber' => (int) $volumeNumber
|
||||
'volumeNumber' => (int) $volumeNumber,
|
||||
], 200);
|
||||
|
||||
} catch (MangaNotFoundException $e) {
|
||||
return $this->json([
|
||||
'error' => 'Manga not found',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], 404);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$statusCode = str_contains($e->getMessage(), 'not found') ? 404 : 400;
|
||||
|
||||
return $this->json([
|
||||
'error' => 'Invalid request',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], $statusCode);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'error' => 'Import failed',
|
||||
'details' => $e->getMessage()
|
||||
'details' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
@@ -105,8 +102,9 @@ final class ImportVolumeController extends AbstractController
|
||||
if (!$uploadedFile->isValid()) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage()
|
||||
'message' => 'The uploaded file is not valid: '.$uploadedFile->getErrorMessage(),
|
||||
];
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
@@ -115,7 +113,7 @@ final class ImportVolumeController extends AbstractController
|
||||
if ($uploadedFile->getSize() > $maxSize) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'The uploaded file is too large. Allowed size is 500MB.'
|
||||
'message' => 'The uploaded file is too large. Allowed size is 500MB.',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -126,7 +124,7 @@ final class ImportVolumeController extends AbstractController
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
$errors[] = [
|
||||
'propertyPath' => 'file',
|
||||
'message' => 'Please upload a valid CBZ file'
|
||||
'message' => 'Please upload a valid CBZ file',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ readonly class ChapterCollection
|
||||
public int $page,
|
||||
public int $limit,
|
||||
public bool $hasNextPage,
|
||||
public bool $hasPreviousPage
|
||||
public bool $hasPreviousPage,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ readonly class FilenameMatchCollection
|
||||
{
|
||||
/**
|
||||
* @param FilenameMatchItem[] $matches
|
||||
* @param string[] $possibleTitles
|
||||
* @param string[] $possibleTitles
|
||||
*/
|
||||
public function __construct(
|
||||
public array $matches,
|
||||
public ?float $chapterNumber,
|
||||
public ?int $volumeNumber,
|
||||
public array $possibleTitles
|
||||
public array $possibleTitles,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ readonly class FilenameMatchItem
|
||||
public ?string $thumbnailUrl,
|
||||
public int $matchScore,
|
||||
public ?float $chapterNumber,
|
||||
public ?float $volumeNumber
|
||||
public ?float $volumeNumber,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
|
||||
readonly class MangaCollection
|
||||
{
|
||||
public function __construct(
|
||||
@@ -13,7 +11,7 @@ readonly class MangaCollection
|
||||
public int $page,
|
||||
public int $limit,
|
||||
public bool $hasNextPage,
|
||||
public bool $hasPreviousPage
|
||||
public bool $hasPreviousPage,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ readonly class MangaDetail
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating,
|
||||
public bool $monitored
|
||||
public bool $monitored,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class MangaListItem
|
||||
{
|
||||
@@ -20,7 +19,7 @@ readonly class MangaListItem
|
||||
public array $genres,
|
||||
public string $status,
|
||||
public ?float $rating,
|
||||
public DateTimeImmutable $createdAt,
|
||||
public \DateTimeImmutable $createdAt,
|
||||
public bool $monitored = false,
|
||||
public int $chaptersTotal = 0,
|
||||
public int $chaptersScraped = 0,
|
||||
|
||||
@@ -6,7 +6,7 @@ readonly class MangaSearchCollection
|
||||
{
|
||||
public function __construct(
|
||||
/** @var MangaSearchItem[] */
|
||||
public array $items
|
||||
public array $items,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ readonly class MangaSearchItem
|
||||
public string $status,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating
|
||||
public ?float $rating,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaDirectlyProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -13,11 +14,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
new Post(
|
||||
uriTemplate: '/mangas/create',
|
||||
processor: CreateMangaDirectlyProcessor::class,
|
||||
openapiContext: [
|
||||
'summary' => 'Create a new manga directly',
|
||||
'description' => 'Creates a new manga with provided data'
|
||||
]
|
||||
)
|
||||
openapi: new Operation(
|
||||
summary: 'Create a new manga directly',
|
||||
description: 'Creates a new manga with provided data'
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class CreateMangaDirectlyResource
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\RequestBody;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -13,11 +15,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
new Post(
|
||||
uriTemplate: '/mangas/create-from-mangadex',
|
||||
processor: CreateMangaProcessor::class,
|
||||
openapiContext: [
|
||||
'summary' => 'Create a new manga from Mangadex',
|
||||
'description' => 'Creates a new manga by fetching its data from Mangadex using an external ID',
|
||||
'requestBody' => [
|
||||
'content' => [
|
||||
openapi: new Operation(
|
||||
summary: 'Create a new manga from Mangadex',
|
||||
description: 'Creates a new manga by fetching its data from Mangadex using an external ID',
|
||||
requestBody: new RequestBody(
|
||||
content: new \ArrayObject([
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
@@ -25,15 +27,15 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
'properties' => [
|
||||
'externalId' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The Mangadex ID of the manga'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
'description' => 'The Mangadex ID of the manga',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
)
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class CreateMangaResource
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\Parameter;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\DeleteCbzProcessor;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteCbzProvider;
|
||||
|
||||
@@ -15,36 +17,34 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteCbzProvider
|
||||
provider: DeleteCbzProvider::class,
|
||||
processor: DeleteCbzProcessor::class,
|
||||
name: 'delete_cbz',
|
||||
openapiContext: [
|
||||
'summary' => 'Delete chapter CBZ file',
|
||||
'description' => 'Removes the CBZ file for a specific chapter and updates the chapter accordingly',
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'in' => 'path',
|
||||
'required' => true,
|
||||
'schema' => [
|
||||
'type' => 'string'
|
||||
],
|
||||
'description' => 'The chapter ID'
|
||||
]
|
||||
openapi: new Operation(
|
||||
summary: 'Delete chapter CBZ file',
|
||||
description: 'Removes the CBZ file for a specific chapter and updates the chapter accordingly',
|
||||
parameters: [
|
||||
new Parameter(
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: ['type' => 'string'],
|
||||
description: 'The chapter ID'
|
||||
),
|
||||
],
|
||||
'responses' => [
|
||||
responses: [
|
||||
'204' => [
|
||||
'description' => 'CBZ file successfully deleted'
|
||||
'description' => 'CBZ file successfully deleted',
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Chapter or CBZ file not found'
|
||||
]
|
||||
'description' => 'Chapter or CBZ file not found',
|
||||
],
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class DeleteCbzResource
|
||||
{
|
||||
public function __construct(
|
||||
public string $id
|
||||
public string $id,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\Parameter;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\DeleteChapterProcessor;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteChapterProvider;
|
||||
|
||||
@@ -15,36 +17,34 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteChapterProv
|
||||
provider: DeleteChapterProvider::class,
|
||||
processor: DeleteChapterProcessor::class,
|
||||
name: 'delete_chapter',
|
||||
openapiContext: [
|
||||
'summary' => 'Delete a chapter (soft delete)',
|
||||
'description' => 'Marks a chapter as deleted by setting its visibility to false',
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'in' => 'path',
|
||||
'required' => true,
|
||||
'schema' => [
|
||||
'type' => 'string'
|
||||
],
|
||||
'description' => 'The chapter ID'
|
||||
]
|
||||
openapi: new Operation(
|
||||
summary: 'Delete a chapter (soft delete)',
|
||||
description: 'Marks a chapter as deleted by setting its visibility to false',
|
||||
parameters: [
|
||||
new Parameter(
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: ['type' => 'string'],
|
||||
description: 'The chapter ID'
|
||||
),
|
||||
],
|
||||
'responses' => [
|
||||
responses: [
|
||||
'204' => [
|
||||
'description' => 'Chapter successfully deleted'
|
||||
'description' => 'Chapter successfully deleted',
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Chapter not found'
|
||||
]
|
||||
'description' => 'Chapter not found',
|
||||
],
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class DeleteChapterResource
|
||||
{
|
||||
public function __construct(
|
||||
public string $id
|
||||
public string $id,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\Parameter;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\DeleteMangaProcessor;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteMangaProvider;
|
||||
|
||||
@@ -15,36 +17,34 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteMangaProvid
|
||||
provider: DeleteMangaProvider::class,
|
||||
processor: DeleteMangaProcessor::class,
|
||||
name: 'delete_manga',
|
||||
openapiContext: [
|
||||
'summary' => 'Delete a manga',
|
||||
'description' => 'Permanently deletes a manga and all its associated chapters',
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'in' => 'path',
|
||||
'required' => true,
|
||||
'schema' => [
|
||||
'type' => 'string'
|
||||
],
|
||||
'description' => 'The manga ID'
|
||||
]
|
||||
openapi: new Operation(
|
||||
summary: 'Delete a manga',
|
||||
description: 'Permanently deletes a manga and all its associated chapters',
|
||||
parameters: [
|
||||
new Parameter(
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: ['type' => 'string'],
|
||||
description: 'The manga ID'
|
||||
),
|
||||
],
|
||||
'responses' => [
|
||||
responses: [
|
||||
'204' => [
|
||||
'description' => 'Manga successfully deleted'
|
||||
'description' => 'Manga successfully deleted',
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Manga not found'
|
||||
]
|
||||
'description' => 'Manga not found',
|
||||
],
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class DeleteMangaResource
|
||||
{
|
||||
public function __construct(
|
||||
public string $id
|
||||
public string $id,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DownloadCbzProvid
|
||||
provider: DownloadCbzProvider::class,
|
||||
output: false,
|
||||
name: 'download_chapter_cbz'
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class DownloadCbzResource
|
||||
{
|
||||
public function __construct(
|
||||
public string $id
|
||||
public string $id,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DownloadVolumePro
|
||||
provider: DownloadVolumeProvider::class,
|
||||
output: false,
|
||||
name: 'download_manga_volume'
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class DownloadVolumeResource
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public int $volume
|
||||
public int $volume,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\EditMangaProcessor;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@@ -15,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
uriTemplate: '/mangas/{id}/edit',
|
||||
processor: EditMangaProcessor::class,
|
||||
provider: GetMangaStateProvider::class,
|
||||
openapiContext: [
|
||||
'summary' => 'Edit an existing manga',
|
||||
'description' => 'Updates an existing manga with provided data (partial update supported)'
|
||||
]
|
||||
)
|
||||
openapi: new Operation(
|
||||
summary: 'Edit an existing manga',
|
||||
description: 'Updates an existing manga with provided data (partial update supported)'
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class EditMangaResource
|
||||
@@ -49,7 +50,7 @@ class EditMangaResource
|
||||
#[Assert\Type(type: 'array', message: 'Les slugs alternatifs doivent être une liste')]
|
||||
#[Assert\All([
|
||||
new Assert\Type('string'),
|
||||
new Assert\Regex(pattern: '/^[a-z0-9-]+$/', message: 'Chaque slug alternatif ne peut contenir que des lettres minuscules, des chiffres et des tirets')
|
||||
new Assert\Regex(pattern: '/^[a-z0-9-]+$/', message: 'Chaque slug alternatif ne peut contenir que des lettres minuscules, des chiffres et des tirets'),
|
||||
])]
|
||||
public ?array $alternativeSlugs = null;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\EditMultipleChaptersProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -15,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
processor: EditMultipleChaptersProcessor::class,
|
||||
input: EditMultipleChaptersResource::class,
|
||||
status: 200,
|
||||
openapiContext: [
|
||||
'summary' => 'Edit multiple chapters',
|
||||
'description' => 'Updates title and/or volume for multiple chapters in a single request'
|
||||
]
|
||||
)
|
||||
openapi: new Operation(
|
||||
summary: 'Edit multiple chapters',
|
||||
description: 'Updates title and/or volume for multiple chapters in a single request'
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class EditMultipleChaptersResource
|
||||
@@ -27,7 +28,7 @@ class EditMultipleChaptersResource
|
||||
public function __construct(
|
||||
#[Assert\NotBlank(message: 'La liste des chapitres est obligatoire')]
|
||||
#[Assert\Count(min: 1, minMessage: 'Vous devez spécifier au moins un chapitre')]
|
||||
public readonly array $chapters
|
||||
public readonly array $chapters,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -37,7 +38,7 @@ readonly class ChapterEditData
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public ?string $title = null,
|
||||
public ?int $volume = null
|
||||
public ?int $volume = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\RequestBody;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\FetchMangaChaptersProcessor;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -15,13 +17,12 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
processor: FetchMangaChaptersProcessor::class,
|
||||
status: 202,
|
||||
description: 'Déclenche la récupération des chapitres d\'un manga',
|
||||
openapiContext: [
|
||||
'summary' => 'Récupérer les chapitres d\'un manga',
|
||||
'description' => 'Lance le processus de récupération des chapitres depuis la source externe pour un manga donné',
|
||||
'requestBody' => [
|
||||
'description' => 'Données requises pour récupérer les chapitres',
|
||||
'required' => true,
|
||||
'content' => [
|
||||
openapi: new Operation(
|
||||
summary: 'Récupérer les chapitres d\'un manga',
|
||||
description: 'Lance le processus de récupération des chapitres depuis la source externe pour un manga donné',
|
||||
requestBody: new RequestBody(
|
||||
description: 'Données requises pour récupérer les chapitres',
|
||||
content: new \ArrayObject([
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
@@ -31,23 +32,24 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
// 'format' => 'uuid',
|
||||
'description' => 'L\'identifiant unique du manga',
|
||||
// 'example' => '123e4567-e89b-12d3-a456-426614174000'
|
||||
]
|
||||
],
|
||||
],
|
||||
'required' => ['mangaId']
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'required' => ['mangaId'],
|
||||
],
|
||||
],
|
||||
]),
|
||||
required: true
|
||||
),
|
||||
responses: [
|
||||
'202' => [
|
||||
'description' => 'Demande de récupération acceptée et mise en file d\'attente'
|
||||
'description' => 'Demande de récupération acceptée et mise en file d\'attente',
|
||||
],
|
||||
'422' => [
|
||||
'description' => 'Données de validation invalides'
|
||||
]
|
||||
'description' => 'Données de validation invalides',
|
||||
],
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class FetchMangaChaptersResource
|
||||
@@ -55,7 +57,7 @@ class FetchMangaChaptersResource
|
||||
public function __construct(
|
||||
#[Assert\NotBlank(message: 'L\'identifiant du manga est obligatoire')]
|
||||
// #[Assert\Uuid(message: 'L\'identifiant du manga doit être un UUID valide')]
|
||||
public string $mangaId
|
||||
public string $mangaId,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\FilenameMatchCollection;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\Parameter;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByFilenameStateProvider;
|
||||
|
||||
#[ApiResource(
|
||||
@@ -15,22 +16,19 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByF
|
||||
new Get(
|
||||
uriTemplate: '/manga-matches',
|
||||
provider: FindMangaMatchByFilenameStateProvider::class,
|
||||
openapiContext: [
|
||||
'summary' => 'Trouve des correspondances de manga à partir d\'un nom de fichier',
|
||||
'description' => 'Analyse un nom de fichier (cbz/cbr) et trouve les mangas correspondants dans la base de données. Extrait automatiquement le titre, le numéro de chapitre et le numéro de volume du nom de fichier.',
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'filename',
|
||||
'in' => 'query',
|
||||
'required' => true,
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'example' => 'one-piece_vol108_ch1094.cbz'
|
||||
],
|
||||
'description' => 'Nom du fichier à analyser (avec ou sans extension .cbz/.cbr)'
|
||||
]
|
||||
openapi: new Operation(
|
||||
summary: 'Trouve des correspondances de manga à partir d\'un nom de fichier',
|
||||
description: 'Analyse un nom de fichier (cbz/cbr) et trouve les mangas correspondants dans la base de données. Extrait automatiquement le titre, le numéro de chapitre et le numéro de volume du nom de fichier.',
|
||||
parameters: [
|
||||
new Parameter(
|
||||
name: 'filename',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: ['type' => 'string', 'example' => 'one-piece_vol108_ch1094.cbz'],
|
||||
description: 'Nom du fichier à analyser (avec ou sans extension .cbz/.cbr)'
|
||||
),
|
||||
],
|
||||
'responses' => [
|
||||
responses: [
|
||||
'200' => [
|
||||
'description' => 'Correspondances trouvées',
|
||||
'content' => [
|
||||
@@ -50,31 +48,31 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByF
|
||||
'alternativeSlugs' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'string'],
|
||||
'description' => 'Slugs alternatifs'
|
||||
'description' => 'Slugs alternatifs',
|
||||
],
|
||||
'thumbnailUrl' => ['type' => 'string', 'nullable' => true, 'description' => 'URL de la miniature'],
|
||||
'matchScore' => ['type' => 'integer', 'description' => 'Score de correspondance (plus élevé = meilleure correspondance)'],
|
||||
'chapterNumber' => ['type' => 'number', 'nullable' => true, 'description' => 'Numéro de chapitre extrait'],
|
||||
'volumeNumber' => ['type' => 'number', 'nullable' => true, 'description' => 'Numéro de volume extrait']
|
||||
]
|
||||
]
|
||||
'volumeNumber' => ['type' => 'number', 'nullable' => true, 'description' => 'Numéro de volume extrait'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'chapterNumber' => [
|
||||
'type' => 'number',
|
||||
'nullable' => true,
|
||||
'description' => 'Numéro de chapitre extrait du nom de fichier'
|
||||
'description' => 'Numéro de chapitre extrait du nom de fichier',
|
||||
],
|
||||
'volumeNumber' => [
|
||||
'type' => 'number',
|
||||
'nullable' => true,
|
||||
'description' => 'Numéro de volume extrait du nom de fichier'
|
||||
'description' => 'Numéro de volume extrait du nom de fichier',
|
||||
],
|
||||
'possibleTitles' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'string'],
|
||||
'description' => 'Variantes de titres générées à partir du nom de fichier'
|
||||
]
|
||||
]
|
||||
'description' => 'Variantes de titres générées à partir du nom de fichier',
|
||||
],
|
||||
],
|
||||
],
|
||||
'example' => [
|
||||
'matches' => [
|
||||
@@ -86,19 +84,19 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByF
|
||||
'thumbnailUrl' => 'https://example.com/thumb.jpg',
|
||||
'matchScore' => 100,
|
||||
'chapterNumber' => 1094.0,
|
||||
'volumeNumber' => 108
|
||||
]
|
||||
'volumeNumber' => 108,
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'400' => [
|
||||
'description' => 'Nom de fichier manquant ou invalide'
|
||||
]
|
||||
'description' => 'Nom de fichier manquant ou invalide',
|
||||
],
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
]
|
||||
)]
|
||||
class FindMangaMatchByFilenameResource
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user