Les chapitres partageant le même pagesDirectory non-null et le même volume non-null (import CBZ en bloc) sont fusionnés en un seul item isVolumeGroup=true côté Application, avec volumeChaptersRange et volumeChapterCount. Le frontend affiche "Vol. X — Chapitres Y-Z" à la place de N lignes identiques.
123 lines
4.2 KiB
PHP
123 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Domain\Manga\Application\QueryHandler;
|
|
|
|
use App\Domain\Manga\Application\Query\GetMangaChapters;
|
|
use App\Domain\Manga\Application\Response\ChapterListResponse;
|
|
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
|
|
{
|
|
$manga = $this->mangaRepository->findById($query->mangaId);
|
|
if (!$manga) {
|
|
throw new MangaNotFoundException();
|
|
}
|
|
|
|
$allChapters = $this->mangaRepository->findAllChapters(
|
|
mangaId: $query->mangaId,
|
|
sortOrder: 'asc'
|
|
);
|
|
|
|
$grouped = $this->groupChapters($allChapters);
|
|
|
|
if ($query->sortOrder === 'desc') {
|
|
usort($grouped, fn (ChapterResponse $a, ChapterResponse $b) => $b->number <=> $a->number);
|
|
}
|
|
|
|
$total = count($grouped);
|
|
$offset = ($query->page - 1) * $query->limit;
|
|
$paginatedChapters = array_slice($grouped, $offset, $query->limit);
|
|
|
|
return new ChapterListResponse(
|
|
chapters: $paginatedChapters,
|
|
total: $total,
|
|
page: $query->page,
|
|
limit: $query->limit
|
|
);
|
|
}
|
|
|
|
/** @param Chapter[] $chapters */
|
|
private function groupChapters(array $chapters): array
|
|
{
|
|
$result = [];
|
|
$currentGroup = [];
|
|
$currentPagesDir = null;
|
|
$currentVolume = null;
|
|
|
|
foreach ($chapters as $chapter) {
|
|
$pagesDir = $chapter->getPagesDirectory();
|
|
$volume = $chapter->getVolume();
|
|
|
|
if ($pagesDir !== null && $volume !== null) {
|
|
if ($pagesDir === $currentPagesDir && $volume === $currentVolume) {
|
|
$currentGroup[] = $chapter;
|
|
} else {
|
|
if (!empty($currentGroup)) {
|
|
$result[] = $this->buildVolumeGroupResponse($currentGroup);
|
|
}
|
|
$currentGroup = [$chapter];
|
|
$currentPagesDir = $pagesDir;
|
|
$currentVolume = $volume;
|
|
}
|
|
} else {
|
|
if (!empty($currentGroup)) {
|
|
$result[] = $this->buildVolumeGroupResponse($currentGroup);
|
|
$currentGroup = [];
|
|
$currentPagesDir = null;
|
|
$currentVolume = null;
|
|
}
|
|
$result[] = new ChapterResponse(
|
|
id: $chapter->getId(),
|
|
number: $chapter->getNumber(),
|
|
title: $chapter->getTitle(),
|
|
volume: $chapter->getVolume(),
|
|
isVisible: $chapter->isVisible(),
|
|
pagesDirectory: $chapter->getPagesDirectory(),
|
|
createdAt: $chapter->getCreatedAt()->format(\DateTimeInterface::RFC3339)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!empty($currentGroup)) {
|
|
$result[] = $this->buildVolumeGroupResponse($currentGroup);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/** @param Chapter[] $group */
|
|
private function buildVolumeGroupResponse(array $group): ChapterResponse
|
|
{
|
|
$first = $group[0];
|
|
$numbers = array_map(fn (Chapter $c) => $c->getNumber(), $group);
|
|
$min = min($numbers);
|
|
$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);
|
|
|
|
return new ChapterResponse(
|
|
id: $first->getId(),
|
|
number: $first->getNumber(),
|
|
title: $first->getTitle(),
|
|
volume: $first->getVolume(),
|
|
isVisible: $first->isVisible(),
|
|
pagesDirectory: $first->getPagesDirectory(),
|
|
createdAt: $first->getCreatedAt()->format(\DateTimeInterface::RFC3339),
|
|
isVolumeGroup: true,
|
|
volumeChaptersRange: $range,
|
|
volumeChapterCount: count($group)
|
|
);
|
|
}
|
|
}
|