refactor(reader): serve pages as static files instead of base64

Replace the per-page API call (base64 payload) with static image URLs
served directly by Caddy from public/images/pages/{chapterId}/.

- LocalImageStorage now stores to public/images/ (was MANGA_DATA_PATH)
- LegacyChapterRepository returns /images/pages/{id}/{file} URLs,
  uses getimagesize() instead of loading file content into memory
- Delete GetChapterPage query/handler/response, ChapterPageResource,
  ChapterPageProvider, PageContent model
- Remove getPageContent() from ChapterRepositoryInterface
- Frontend: loadChapter() fetches chapter + all pages in parallel,
  ReaderPage uses URL instead of base64 data URI, InfiniteReader drops
  lazy-load observer side effect, readerStore drops loadedPages/preload
- GetChapterPagesTest: extract fixture images from CBZ at runtime,
  ignore tests/Fixtures/pages/ in .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-09 22:05:45 +01:00
parent 6875ad4222
commit 322c396165
19 changed files with 300 additions and 755 deletions

View File

@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Domain\Reader\Application\Query;
final readonly class GetChapterPage
{
public function __construct(
private string $chapterId,
private int $pageNumber
) {
}
public function getChapterId(): string
{
return $this->chapterId;
}
public function getPageNumber(): int
{
return $this->pageNumber;
}
}

View File

@@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Domain\Reader\Application\QueryHandler;
use App\Domain\Reader\Application\Query\GetChapterPage;
use App\Domain\Reader\Application\Response\ChapterPageResponse;
use App\Domain\Reader\Domain\Contract\Repository\ChapterRepositoryInterface;
use App\Domain\Reader\Domain\Exception\ChapterNotFoundException;
use App\Domain\Reader\Domain\Exception\PageNotFoundException;
use App\Domain\Reader\Domain\ValueObject\ChapterId;
use App\Domain\Reader\Domain\ValueObject\PageNumber;
final readonly class GetChapterPageHandler
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository
) {
}
public function handle(GetChapterPage $query): ChapterPageResponse
{
$chapterId = new ChapterId($query->getChapterId());
$pageNumber = new PageNumber($query->getPageNumber());
$totalPages = $this->chapterRepository->getTotalPagesForChapter($chapterId);
if ($totalPages === 0) {
throw ChapterNotFoundException::forChapter($chapterId);
}
if ($pageNumber->getValue() > $totalPages) {
throw PageNotFoundException::forPage($chapterId, $pageNumber);
}
$page = $this->chapterRepository->getPageContent($chapterId, $pageNumber);
return new ChapterPageResponse(
$page->getId(),
$page->getPageNumber()->getValue(),
$page->getBase64Content(),
$page->getMimeType(),
$page->getDimensions()
);
}
}

View File

@@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Domain\Reader\Application\Response;
final readonly class ChapterPageResponse
{
public function __construct(
private string $id,
private int $pageNumber,
private string $base64Content,
private string $mimeType,
private array $dimensions
) {
}
public function getId(): string
{
return $this->id;
}
public function getPageNumber(): int
{
return $this->pageNumber;
}
public function getBase64Content(): string
{
return $this->base64Content;
}
public function getMimeType(): string
{
return $this->mimeType;
}
public function getDimensions(): array
{
return $this->dimensions;
}
}