5 Commits

Author SHA1 Message Date
ext.jeremy.guillot@maxicoffee.domains
c311cfe80c refactor(scraping): DDD refactoring — stockage images individuelles
Le domaine Scraping ne génère plus d'archives CBZ ni ne modifie les
entités du domaine Manga directement. Il scrape, stocke les images
individuellement, et émet un événement partagé.

- Suppression : CbzGeneratorInterface, CbzGenerator, CbzGenerationRequest,
  CbzPath, CbzGenerationException
- Suppression : save() de ChapterRepositoryInterface (Scraping)
- Suppression : cbzPath du modèle Chapter (Scraping)
- Ajout : ImageStorageInterface + LocalImageStorage
  (stockage dans {MANGA_DATA_PATH}/pages/{chapterId}/)
- ScrapeChapterHandler utilise ImageStorage au lieu du générateur CBZ

- ChapterScraped déplacé dans Domain/Shared/Domain/Event/
  avec jobId, chapterId, pagesDirectory, pageCount
- Routing Messenger ajouté

- Ajout : ChapterScrapedEventListener + ChapterScrapedMessageHandler
  pour mettre à jour Chapter.pagesDirectory via le Repository Manga

- LegacyChapterRepository en dual-mode :
  pagesDirectory en priorité, fallback cbzPath (backward compat)
- Requêtes prev/next : filtrent pagesDirectory IS NOT NULL OR cbzPath IS NOT NULL
- ChapterContext expose pagesDirectory

- phparkitect.php : App\Domain\Shared\Domain\Event autorisé dans
  les couches Application (correction violations pré-existantes
  ChapterImported/VolumeImported + nouvelle ChapterScraped)

- 218/218 tests passent (+3 nouveaux)
- InMemoryImageStorage créé pour les tests unitaires

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 20:52:16 +01:00
ext.jeremy.guillot@maxicoffee.domains
d444f86315 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
# 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
2026-03-09 20:47:43 +01:00
ext.jeremy.guillot@maxicoffee.domains
7506a7a3c1 style: apply php-cs-fixer formatting (PSR-12)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 20:46:59 +01:00
4cd277aec7 Merge pull request 'fix(migration): DROP INDEX IF EXISTS pour messenger_messages' (#2) from feat/chapter-entity-image-storage into main
All checks were successful
Build and Deploy / deploy (push) Successful in 1m36s
Reviewed-on: #2
2026-03-09 19:36:19 +01:00
02760effe6 Merge pull request 'feat/chapter-entity-image-storage' (#1) from feat/chapter-entity-image-storage into main
All checks were successful
Build and Deploy / deploy (push) Successful in 1m7s
Reviewed-on: #1
2026-03-09 19:25:22 +01:00
227 changed files with 432 additions and 353 deletions

View File

@@ -15,7 +15,7 @@ class SecurityController extends AbstractController
#[Route('/login', name: 'app_login', methods: ['GET', 'POST'])]
public function login(IriConverterInterface $iriConverter, #[CurrentUser] User $user = null): Response
{
if(!$user) {
if (!$user) {
return $this->json([
'error' => 'Invalid credentials'
], 401);

View File

@@ -8,7 +8,6 @@ use App\Form\ContentSourceType;
use App\Manager\AppSettingsManager;
use App\Manager\Toolbar\Factory\ToolbarFactory;
use App\Repository\ContentSourceRepository;
use App\Service\NotificationService;
use App\Service\Scraper\MangaScraperService;
use Doctrine\ORM\EntityManagerInterface;

View File

@@ -46,7 +46,7 @@ class TestController extends AbstractController
$changed = 0;
foreach ($mangas as $manga) {
//si getImageUrl() retourne un lien sous la forme d'une URL (https ou http)
if($manga->getImageUrl()) {
if ($manga->getImageUrl()) {
$imageUrls = $this->processAndSaveImage($manga->getImageUrl());
$manga->setThumbnailUrl($imageUrls['thumbnail']);
$this->mangaRepository->save($manga, true);

View File

@@ -8,5 +8,6 @@ final readonly class ConvertFileCommand
public string $filePath,
public string $originalFilename,
public int $fileSize
) {}
) {
}
}

View File

@@ -14,7 +14,8 @@ final readonly class ConvertFileCommandHandler
public function __construct(
private ConversionServiceInterface $conversionService
) {}
) {
}
public function handle(ConvertFileCommand $command): ConversionResponse
{

View File

@@ -11,7 +11,8 @@ final readonly class ConversionResponse
public string $outputFilename,
public int $originalFileSize,
public int $convertedFileSize
) {}
) {
}
public static function fromConversionResult(ConversionResult $result): self
{

View File

@@ -8,7 +8,8 @@ final readonly class ConversionRequest
private string $filePath,
private string $originalFilename,
private int $fileSize
) {}
) {
}
public function getFilePath(): string
{

View File

@@ -9,7 +9,8 @@ final readonly class ConversionResult
private string $outputFilename,
private int $originalFileSize,
private int $convertedFileSize
) {}
) {
}
public function getConvertedFilePath(): string
{

View File

@@ -17,7 +17,8 @@ final class ConvertFileController extends AbstractController
{
public function __construct(
private readonly ConvertFileCommandHandler $commandHandler
) {}
) {
}
public function __invoke(Request $request): Response
{

View File

@@ -8,5 +8,6 @@ readonly class ChapterEditData
public string $id,
public ?string $title = null,
public ?int $volume = null
) {}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class CheckMonitoredMangas
{
public function __construct(
public ?DateTimeImmutable $since = null
) {}
) {
}
}

View File

@@ -15,5 +15,6 @@ readonly class CreateManga
public ?string $externalId,
public ?string $imageUrl,
public ?float $rating
) {}
}
) {
}
}

View File

@@ -6,5 +6,6 @@ readonly class CreateMangaFromMangadex
{
public function __construct(
public string $externalId
) {}
}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class DeleteCbz implements CommandInterface
{
public function __construct(
public string $chapterId
) {}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class DeleteChapter implements CommandInterface
{
public function __construct(
public string $chapterId
) {}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class DeleteManga implements CommandInterface
{
public function __construct(
public string $mangaId
) {}
) {
}
}

View File

@@ -14,5 +14,6 @@ readonly class EditManga
public ?string $status = null,
public ?float $rating = null,
public ?array $alternativeSlugs = null
) {}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class EditMultipleChapters
*/
public function __construct(
public array $chapters
) {}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class FetchMangaChapters
{
public function __construct(
public MangaId $mangaId
) {}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class ImportChapter
public string $mangaId,
public float $chapterNumber,
public string $fileBinary
) {}
) {
}
}

View File

@@ -8,9 +8,6 @@ readonly class ImportVolume
public string $mangaId,
public int $volumeNumber,
public string $fileBinary
) {}
) {
}
}

View File

@@ -8,5 +8,6 @@ readonly class RefreshMangaChapters
{
public function __construct(
public MangaId $mangaId
) {}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class ToggleMangaMonitoring
public function __construct(
public MangaId $mangaId,
public bool $enabled
) {}
) {
}
}

View File

@@ -14,7 +14,8 @@ readonly class CheckMonitoredMangasHandler
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private MessageBusInterface $commandBus
) {}
) {
}
public function handle(CheckMonitoredMangas $command): void
{

View File

@@ -20,7 +20,8 @@ readonly class CreateMangaFromMangadexHandler
private MangaRepositoryInterface $mangaRepository,
private ImageProcessorInterface $imageProcessor,
private EventDispatcherInterface $eventDispatcher
) {}
) {
}
public function handle(CreateMangaFromMangadex $command): void
{

View File

@@ -21,7 +21,8 @@ readonly class CreateMangaHandler
private MangaRepositoryInterface $mangaRepository,
private ImageProcessorInterface $imageProcessor,
private MessageBusInterface $messageBus
) {}
) {
}
public function handle(CreateManga $command): void
{

View File

@@ -15,7 +15,8 @@ readonly class DeleteCbzHandler implements CommandHandlerInterface
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private FileServiceInterface $fileService
) {}
) {
}
public function handle(CommandInterface $command): void
{

View File

@@ -12,7 +12,8 @@ readonly class DeleteChapterHandler implements CommandHandlerInterface
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(CommandInterface $command): void
{

View File

@@ -12,7 +12,8 @@ readonly class DeleteMangaHandler implements CommandHandlerInterface
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(CommandInterface $command): void
{

View File

@@ -11,7 +11,8 @@ readonly class EditMangaHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(EditManga $command): void
{

View File

@@ -10,7 +10,8 @@ readonly class EditMultipleChaptersHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(EditMultipleChapters $command): void
{

View File

@@ -13,7 +13,8 @@ readonly class FetchMangaChaptersHandler
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private ChapterSynchronizationServiceInterface $chapterSynchronizationService
) {}
) {
}
public function handle(FetchMangaChapters $command): void
{
@@ -23,7 +24,7 @@ readonly class FetchMangaChaptersHandler
throw new MangaNotFoundException();
}
if($manga->getExternalId() === null){
if ($manga->getExternalId() === null) {
throw new MangadexApiException("Manga has no external_id");
}

View File

@@ -13,7 +13,8 @@ readonly class ImportChapterHandler
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private MangaPathManagerInterface $pathManager
) {}
) {
}
public function handle(ImportChapter $command): void
{

View File

@@ -12,7 +12,8 @@ readonly class ImportVolumeHandler
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private MangaPathManagerInterface $pathManager
) {}
) {
}
public function handle(ImportVolume $command): void
{

View File

@@ -16,7 +16,8 @@ readonly class RefreshMangaChaptersHandler
private MangaRepositoryInterface $mangaRepository,
private ChapterSynchronizationServiceInterface $chapterSynchronizationService,
private MessageBusInterface $eventBus
) {}
) {
}
public function handle(RefreshMangaChapters $command): void
{

View File

@@ -10,7 +10,8 @@ readonly class ToggleMangaMonitoringHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(ToggleMangaMonitoring $command): void
{

View File

@@ -12,7 +12,8 @@ readonly class ChapterImportedEventListener
{
public function __construct(
private MangaRepositoryInterface $mangaRepository,
) {}
) {
}
public function __invoke(ChapterImported $event): void
{

View File

@@ -11,7 +11,8 @@ readonly class MangaCreatedEventListener
{
public function __construct(
private FetchMangaChaptersHandler $fetchMangaChaptersHandler
) {}
) {
}
public function __invoke(MangaCreated $event): void
{

View File

@@ -12,7 +12,8 @@ readonly class VolumeImportedEventListener
{
public function __construct(
private MangaRepositoryInterface $mangaRepository,
) {}
) {
}
public function __invoke(VolumeImported $event): void
{

View File

@@ -8,5 +8,6 @@ readonly class DownloadCbz implements QueryInterface
{
public function __construct(
public string $chapterId
) {}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class DownloadVolume implements QueryInterface
public function __construct(
public string $mangaId,
public int $volume
) {}
) {
}
}

View File

@@ -11,4 +11,3 @@ readonly class FindMangaMatchByFilename
) {
}
}

View File

@@ -6,5 +6,6 @@ readonly class GetMangaById
{
public function __construct(
public string $id
) {}
) {
}
}

View File

@@ -6,5 +6,6 @@ readonly class GetMangaBySlug
{
public function __construct(
public string $slug
) {}
}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class GetMangaChapters
public ?int $page = 1,
public ?int $limit = 20,
public ?string $sortOrder = 'desc'
) {}
}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class GetMangaList
public ?int $limit = 20,
public ?string $sortBy = 'title',
public ?string $sortOrder = 'asc'
) {}
}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class MonitoringCriteria
public function __construct(
public bool $enabled,
public ?DateTimeImmutable $lastCheckBefore = null
) {}
) {
}
}

View File

@@ -10,4 +10,4 @@ readonly class SearchLocalManga
public int $limit = 20
) {
}
}
}

View File

@@ -6,5 +6,6 @@ readonly class SearchManga
{
public function __construct(
public string $title
) {}
}
) {
}
}

View File

@@ -18,7 +18,8 @@ readonly class DownloadCbzHandler implements QueryHandlerInterface
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private FileServiceInterface $fileService
) {}
) {
}
public function handle(QueryInterface $query): ResponseInterface
{

View File

@@ -17,7 +17,8 @@ readonly class DownloadVolumeHandler implements QueryHandlerInterface
public function __construct(
private MangaRepositoryInterface $mangaRepository,
private FileServiceInterface $fileService
) {}
) {
}
public function handle(QueryInterface $query): ResponseInterface
{
@@ -43,7 +44,8 @@ readonly class DownloadVolumeHandler implements QueryHandlerInterface
$cbzPaths[] = $chapter->getPagesDirectory();
}
$volumeName = sprintf('%s_vol%d',
$volumeName = sprintf(
'%s_vol%d',
$manga->getSlug()->getValue(),
$query->volume
);

View File

@@ -58,7 +58,7 @@ readonly class FindMangaMatchByFilenameHandler
}
// Trier les résultats par score de correspondance (du plus élevé au plus faible)
usort($matches, fn($a, $b) => $b->matchScore <=> $a->matchScore);
usort($matches, fn ($a, $b) => $b->matchScore <=> $a->matchScore);
return new MangaMatchResponse(
matches: $matches,
@@ -119,4 +119,3 @@ readonly class FindMangaMatchByFilenameHandler
return $score;
}
}

View File

@@ -11,7 +11,8 @@ readonly class GetMangaByIdHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(GetMangaById $query): MangaResponse
{

View File

@@ -12,7 +12,8 @@ readonly class GetMangaBySlugHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(GetMangaBySlug $query): MangaResponse
{

View File

@@ -8,11 +8,13 @@ use App\Domain\Manga\Application\Response\ChapterResponse;
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
use App\Domain\Manga\Domain\Model\Chapter;
readonly class GetMangaChaptersHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(GetMangaChapters $query): ChapterListResponse
{
@@ -48,4 +50,4 @@ readonly class GetMangaChaptersHandler
limit: $query->limit
);
}
}
}

View File

@@ -10,7 +10,8 @@ readonly class GetMangaListHandler
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
) {
}
public function handle(GetMangaList $query): MangaListResponse
{
@@ -30,4 +31,4 @@ readonly class GetMangaListHandler
limit: $query->limit
);
}
}
}

View File

@@ -12,7 +12,8 @@ readonly class SearchLocalMangaHandler
{
public function __construct(
private MangaRepositoryInterface $repository
) {}
) {
}
public function handle(SearchLocalManga $query): MangaListResponse
{

View File

@@ -12,7 +12,8 @@ readonly class SearchMangaHandler
{
public function __construct(
private MangaProviderInterface $mangaProvider
) {}
) {
}
public function handle(SearchManga $query): MangaSearchResponse
{
@@ -40,4 +41,4 @@ readonly class SearchMangaHandler
)
);
}
}
}

View File

@@ -9,7 +9,8 @@ readonly class ChapterListResponse
public int $total,
public int $page,
public int $limit
) {}
) {
}
public function getTotalPages(): int
{
@@ -25,4 +26,4 @@ readonly class ChapterListResponse
{
return $this->page > 1;
}
}
}

View File

@@ -12,5 +12,6 @@ readonly class ChapterResponse
public bool $isVisible,
public ?string $pagesDirectory,
public string $createdAt
) {}
) {
}
}

View File

@@ -9,5 +9,6 @@ readonly class DownloadResponse implements ResponseInterface
{
public function __construct(
public Response $httpResponse
) {}
) {
}
}

View File

@@ -9,7 +9,8 @@ readonly class MangaListResponse
public int $total,
public int $page,
public int $limit
) {}
) {
}
public function getTotalPages(): int
{
@@ -25,4 +26,4 @@ readonly class MangaListResponse
{
return $this->page > 1;
}
}
}

View File

@@ -18,4 +18,3 @@ readonly class MangaMatchItem
) {
}
}

View File

@@ -27,4 +27,3 @@ readonly class MangaMatchResponse
return $this->matches[0] ?? null;
}
}

View File

@@ -19,5 +19,6 @@ readonly class MangaResponse
public ?string $thumbnailUrl,
public ?float $rating,
public bool $monitored
) {}
) {
}
}

View File

@@ -17,5 +17,6 @@ readonly class MangaSearchItem
public ?string $imageUrl,
public ?string $thumbnailUrl,
public ?float $rating
) {}
) {
}
}

View File

@@ -14,4 +14,4 @@ readonly class MangaSearchResponse
{
$this->items = $items;
}
}
}

View File

@@ -12,7 +12,8 @@ readonly class SearchLocalMangaResponse
public int $total,
public int $page,
public int $limit
) {}
) {
}
public function hasNextPage(): bool
{
@@ -23,4 +24,4 @@ readonly class SearchLocalMangaResponse
{
return $this->page > 1;
}
}
}

View File

@@ -93,4 +93,4 @@ interface MangadexClientInterface
* }
*/
public function getManga(string $mangaId): array;
}
}

View File

@@ -9,6 +9,6 @@ use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
interface MangaProviderInterface
{
public function search(string $title): MangaCollection;
public function findByExternalId(ExternalId $externalId): ?Manga;
}
}

View File

@@ -13,4 +13,4 @@ interface ImageProcessorInterface
* @throws \Exception
*/
public function createThumbnail(string $originalImagePath): string;
}
}

View File

@@ -8,5 +8,6 @@ readonly class ChapterReadyForScraping
{
public function __construct(
public ChapterId $chapterId
) {}
) {
}
}

View File

@@ -7,5 +7,6 @@ readonly class MangaCreated
public function __construct(
public string $mangaId,
public string $externalId
) {}
) {
}
}

View File

@@ -4,4 +4,4 @@ namespace App\Domain\Manga\Domain\Exception;
class InvalidExternalIdException extends MangaDomainException
{
}
}

View File

@@ -4,4 +4,4 @@ namespace App\Domain\Manga\Domain\Exception;
class InvalidMangaIdException extends MangaDomainException
{
}
}

View File

@@ -4,4 +4,4 @@ namespace App\Domain\Manga\Domain\Exception;
class InvalidMangaSlugException extends MangaDomainException
{
}
}

View File

@@ -4,4 +4,4 @@ namespace App\Domain\Manga\Domain\Exception;
class InvalidMangaTitleException extends MangaDomainException
{
}
}

View File

@@ -8,4 +8,4 @@ class MangadexApiException extends MangaDomainException
{
parent::__construct($message, 0, $previous);
}
}
}

View File

@@ -8,4 +8,4 @@ class MangadexAuthenticationException extends MangaDomainException
{
parent::__construct($message, 0, $previous);
}
}
}

View File

@@ -42,4 +42,3 @@ readonly class AnalyzedFilename
return $this->volumeNumber !== null;
}
}

View File

@@ -17,7 +17,8 @@ class Chapter
private ?string $pagesDirectory = null,
private int $pageCount = 0,
private \DateTimeImmutable $createdAt = new \DateTimeImmutable()
) {}
) {
}
public function getId(): string
{

View File

@@ -24,4 +24,4 @@ readonly class MangaCollection
{
return count($this->items);
}
}
}

View File

@@ -6,7 +6,8 @@ readonly class ChapterId
{
public function __construct(
private string $value
) {}
) {
}
public function getValue(): string
{

View File

@@ -21,4 +21,3 @@ readonly class ChapterNumber
return $this->value;
}
}

View File

@@ -18,4 +18,4 @@ readonly class ExternalId
{
return $this->value;
}
}
}

View File

@@ -7,7 +7,8 @@ readonly class ImageUrls
public function __construct(
private string $full,
private string $thumbnail
) {}
) {
}
public function getFull(): string
{
@@ -18,4 +19,4 @@ readonly class ImageUrls
{
return $this->thumbnail;
}
}
}

View File

@@ -23,4 +23,4 @@ readonly class MangaId
{
return $this->value === $other->value;
}
}
}

View File

@@ -22,4 +22,4 @@ readonly class MangaSlug
{
return $this->value;
}
}
}

View File

@@ -18,4 +18,4 @@ readonly class MangaTitle
{
return $this->value;
}
}
}

View File

@@ -6,7 +6,8 @@ readonly class MonitoringStatus
{
public function __construct(
private bool $enabled
) {}
) {
}
public static function enabled(): self
{

View File

@@ -21,4 +21,3 @@ readonly class VolumeNumber
return $this->value;
}
}

View File

@@ -18,7 +18,8 @@ final class ImportChapterController extends AbstractController
{
public function __construct(
private readonly ImportChapterHandler $commandHandler
) {}
) {
}
public function __invoke(Request $request): Response
{
@@ -139,7 +140,3 @@ final class ImportChapterController extends AbstractController
return $errors;
}
}

View File

@@ -16,7 +16,8 @@ final class ImportVolumeController extends AbstractController
{
public function __construct(
private readonly ImportVolumeHandler $commandHandler
) {}
) {
}
public function __invoke(Request $request): Response
{
@@ -132,7 +133,3 @@ final class ImportVolumeController extends AbstractController
return $errors;
}
}

View File

@@ -12,5 +12,6 @@ readonly class ChapterCollection
public int $limit,
public bool $hasNextPage,
public bool $hasPreviousPage
) {}
}
) {
}
}

View File

@@ -15,5 +15,6 @@ readonly class ChapterListItem
public bool $isVisible,
public bool $isAvailable,
public string $createdAt
) {}
}
) {
}
}

View File

@@ -18,4 +18,3 @@ readonly class FilenameMatchCollection
) {
}
}

View File

@@ -18,4 +18,3 @@ readonly class FilenameMatchItem
) {
}
}

View File

@@ -14,5 +14,6 @@ readonly class MangaCollection
public int $limit,
public bool $hasNextPage,
public bool $hasPreviousPage
) {}
}
) {
}
}

View File

@@ -22,5 +22,6 @@ readonly class MangaDetail
public ?string $thumbnailUrl,
public ?float $rating,
public bool $monitored
) {}
) {
}
}

View File

@@ -21,5 +21,6 @@ readonly class MangaListItem
public string $status,
public ?float $rating,
public DateTimeImmutable $createdAt,
) {}
) {
}
}

Some files were not shown because too many files have changed in this diff Show More