feat(manga): implémenter la page Découvrir avec recommandations MangaDex
- Endpoint GET /api/manga-discover via DiscoverMangaStateProvider + DiscoverMangaHandler
- Algorithme : top 5 manga de la collection → appel /manga/{id}/recommendation
par source → agrégation avec système de votes (multi-sources = plus pertinent)
- Filtrage : tags exclus (Oneshot, Doujinshi, Self-Published), contentRating,
et suppression des manga déjà en bibliothèque
- Page Vue DiscoverPage.vue : chargement auto au montage, bouton Actualiser,
modal détail, ajout à la bibliothèque
- Adapteurs InMemory de test mis à jour (discover + getMangaRecommendations)
This commit is contained in:
parent
65453c87e5
commit
814fe46ce5
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Query\DiscoverManga;
|
||||
use App\Domain\Manga\Application\Response\MangaSearchItem;
|
||||
use App\Domain\Manga\Application\Response\MangaSearchResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
|
||||
readonly class DiscoverMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $mangaRepository,
|
||||
private MangaProviderInterface $mangaProvider
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(DiscoverManga $query): MangaSearchResponse
|
||||
{
|
||||
$localMangas = $this->mangaRepository->findAll(page: 1, limit: 1000);
|
||||
|
||||
$ownedExternalIds = [];
|
||||
$mangasWithRating = [];
|
||||
foreach ($localMangas as $manga) {
|
||||
if (!$manga->getExternalId()) {
|
||||
continue;
|
||||
}
|
||||
$ownedExternalIds[] = $manga->getExternalId()->getValue();
|
||||
$mangasWithRating[] = $manga;
|
||||
}
|
||||
|
||||
usort($mangasWithRating, fn ($a, $b) => ($b->getRating() ?? 0) <=> ($a->getRating() ?? 0));
|
||||
$sourceIds = array_map(
|
||||
fn (Manga $m) => $m->getExternalId()->getValue(),
|
||||
array_slice($mangasWithRating, 0, 5)
|
||||
);
|
||||
|
||||
$collection = $this->mangaProvider->discover($sourceIds);
|
||||
|
||||
$recommendations = array_values(array_filter(
|
||||
$collection->getItems(),
|
||||
fn (Manga $m) => $m->getExternalId() === null
|
||||
|| !in_array($m->getExternalId()->getValue(), $ownedExternalIds, true)
|
||||
));
|
||||
|
||||
return new MangaSearchResponse(
|
||||
array_map(
|
||||
fn (Manga $manga, int $index) => new MangaSearchItem(
|
||||
id: $index,
|
||||
externalId: $manga->getExternalId()->getValue(),
|
||||
title: $manga->getTitle()->getValue(),
|
||||
slug: $manga->getSlug()->getValue(),
|
||||
description: $manga->getDescription(),
|
||||
author: $manga->getAuthor(),
|
||||
publicationYear: $manga->getPublicationYear(),
|
||||
genres: $manga->getGenres(),
|
||||
status: $manga->getStatus(),
|
||||
imageUrl: $manga->getImageUrl(),
|
||||
thumbnailUrl: $manga->getImageUrls()?->getThumbnail(),
|
||||
rating: $manga->getRating()
|
||||
),
|
||||
$recommendations,
|
||||
array_keys($recommendations)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user