feat: ajout d'une route GetMangaByIdHandler.php et fix de la SearchBar.vue
This commit is contained in:
parent
ed0a075a6c
commit
d9e935f7de
13
src/Domain/Manga/Application/Query/SearchLocalManga.php
Normal file
13
src/Domain/Manga/Application/Query/SearchLocalManga.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\Query;
|
||||
|
||||
readonly class SearchLocalManga
|
||||
{
|
||||
public function __construct(
|
||||
public string $query,
|
||||
public int $page = 1,
|
||||
public int $limit = 20
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ readonly class GetMangaByIdHandler
|
||||
status: $manga->getStatus(),
|
||||
externalId: $manga->getExternalId()?->getValue(),
|
||||
imageUrl: $manga->getImageUrl(),
|
||||
thumbnailUrl: $manga->getImageUrls()->getThumbnail(),
|
||||
rating: $manga->getRating()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Manga\Application\Query\SearchLocalManga;
|
||||
use App\Domain\Manga\Application\Response\MangaListResponse;
|
||||
use App\Domain\Manga\Application\Response\MangaResponse;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Model\Manga;
|
||||
|
||||
readonly class SearchLocalMangaHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MangaRepositoryInterface $repository
|
||||
) {}
|
||||
|
||||
public function handle(SearchLocalManga $query): MangaListResponse
|
||||
{
|
||||
$mangas = $this->repository->search($query->query, $query->page, $query->limit);
|
||||
$total = $this->repository->countSearch($query->query);
|
||||
|
||||
return new MangaListResponse(
|
||||
mangas: array_map(
|
||||
fn (Manga $manga) => new MangaResponse(
|
||||
id: $manga->getId()->getValue(),
|
||||
title: $manga->getTitle()->getValue(),
|
||||
slug: $manga->getSlug()->getValue(),
|
||||
description: $manga->getDescription(),
|
||||
author: $manga->getAuthor(),
|
||||
publicationYear: $manga->getPublicationYear(),
|
||||
genres: $manga->getGenres(),
|
||||
status: $manga->getStatus(),
|
||||
externalId: $manga->getExternalId()?->getValue() ?? '',
|
||||
imageUrl: $manga->getImageUrls()->getFull(),
|
||||
thumbnailUrl: $manga->getImageUrls()->getThumbnail(),
|
||||
rating: $manga->getRating()
|
||||
),
|
||||
$mangas
|
||||
),
|
||||
total: $total,
|
||||
page: $query->page,
|
||||
limit: $query->limit
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ readonly class MangaResponse
|
||||
public string $status,
|
||||
public ?string $externalId,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Domain\Manga\Application\Response;
|
||||
readonly class MangaSearchItem
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $externalId,
|
||||
public string $title,
|
||||
public string $slug,
|
||||
@@ -14,6 +15,7 @@ readonly class MangaSearchItem
|
||||
public array $genres,
|
||||
public string $status,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Application\Response;
|
||||
|
||||
readonly class SearchLocalMangaResponse
|
||||
{
|
||||
/**
|
||||
* @param MangaSearchItem[] $items
|
||||
*/
|
||||
public function __construct(
|
||||
public array $items,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit
|
||||
) {}
|
||||
|
||||
public function hasNextPage(): bool
|
||||
{
|
||||
return $this->total > ($this->page * $this->limit);
|
||||
}
|
||||
|
||||
public function hasPreviousPage(): bool
|
||||
{
|
||||
return $this->page > 1;
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,6 @@ interface MangaRepositoryInterface
|
||||
public function findByExternalId(ExternalId $externalId): ?Manga;
|
||||
public function saveChapter(Chapter $chapter): void;
|
||||
public function findBySlug(MangaSlug $slug): ?Manga;
|
||||
public function search(string $query, int $page = 1, int $limit = 20): array;
|
||||
public function countSearch(string $query): int;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ readonly class MangaListItem
|
||||
public string $description,
|
||||
public string $slug,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public string $author,
|
||||
public int $publicationYear,
|
||||
public array $genres,
|
||||
@@ -21,4 +22,4 @@ readonly class MangaListItem
|
||||
public ?float $rating,
|
||||
public DateTimeImmutable $createdAt,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ readonly class MangaSearchItem
|
||||
public array $genres,
|
||||
public string $status,
|
||||
public ?string $imageUrl,
|
||||
public ?string $thumbnailUrl,
|
||||
public ?float $rating
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProv
|
||||
shortName: 'Manga',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/mangas/{id}',
|
||||
uriTemplate: '/mangas/by-id/{id}',
|
||||
provider: GetMangaStateProvider::class,
|
||||
output: MangaDetail::class,
|
||||
openapiContext: [
|
||||
|
||||
@@ -8,10 +8,10 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchCollection;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchMangaStateProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Manga',
|
||||
shortName: 'Mangadex',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/mangas-search',
|
||||
uriTemplate: '/mangadex-search',
|
||||
openapiContext: [
|
||||
'parameters' => [
|
||||
[
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchCollection;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchLocalMangaStateProvider;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Manga',
|
||||
operations: [
|
||||
new Get(
|
||||
uriTemplate: '/mangas/search',
|
||||
provider: SearchLocalMangaStateProvider::class,
|
||||
output: MangaSearchCollection::class,
|
||||
status: 200,
|
||||
openapiContext: [
|
||||
'summary' => 'Recherche des mangas dans la bibliothèque locale',
|
||||
'description' => 'Recherche des mangas par titre, slug ou auteur (minimum 3 caractères)',
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'q',
|
||||
'in' => 'query',
|
||||
'required' => true,
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'minLength' => 3
|
||||
],
|
||||
'description' => 'Terme de recherche (minimum 3 caractères)'
|
||||
],
|
||||
[
|
||||
'name' => 'page',
|
||||
'in' => 'query',
|
||||
'required' => false,
|
||||
'schema' => [
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'minimum' => 1
|
||||
],
|
||||
'description' => 'Numéro de page'
|
||||
],
|
||||
[
|
||||
'name' => 'limit',
|
||||
'in' => 'query',
|
||||
'required' => false,
|
||||
'schema' => [
|
||||
'type' => 'integer',
|
||||
'default' => 20,
|
||||
'minimum' => 1,
|
||||
'maximum' => 50
|
||||
],
|
||||
'description' => 'Nombre de résultats par page'
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Résultats de la recherche',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'items' => [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'$ref' => '#/components/schemas/MangaSearchItem'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'400' => [
|
||||
'description' => 'Paramètres de recherche invalides'
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class SearchLocalMangaResource
|
||||
{
|
||||
}
|
||||
@@ -44,9 +44,10 @@ readonly class GetMangaListStateProvider implements ProviderInterface
|
||||
return new MangaListItem(
|
||||
id: $manga->getId()->getValue(),
|
||||
title: $manga->getTitle()->getValue(),
|
||||
slug: $manga->getSlug()->getValue(),
|
||||
description: $manga->getDescription(),
|
||||
slug: $manga->getSlug()->getValue(),
|
||||
imageUrl: $manga->getImageUrl(),
|
||||
thumbnailUrl: $manga->getImageUrls()->getThumbnail(),
|
||||
author: $manga->getAuthor(),
|
||||
publicationYear: $manga->getPublicationYear(),
|
||||
genres: $manga->getGenres(),
|
||||
@@ -55,4 +56,4 @@ readonly class GetMangaListStateProvider implements ProviderInterface
|
||||
createdAt: $manga->getCreatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Manga\Application\Query\SearchLocalManga;
|
||||
use App\Domain\Manga\Application\QueryHandler\SearchLocalMangaHandler;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaCollection;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaListItem;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
readonly class SearchLocalMangaStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private SearchLocalMangaHandler $handler
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): MangaCollection
|
||||
{
|
||||
$query = $context['filters']['q'] ?? '';
|
||||
|
||||
if (strlen($query) < 3) {
|
||||
throw new BadRequestHttpException('Le terme de recherche doit contenir au moins 3 caractères');
|
||||
}
|
||||
|
||||
$page = (int) ($context['filters']['page'] ?? 1);
|
||||
if ($page < 1) {
|
||||
throw new BadRequestHttpException('Le numéro de page doit être supérieur à 0');
|
||||
}
|
||||
|
||||
$limit = (int) ($context['filters']['limit'] ?? 20);
|
||||
|
||||
$searchQuery = new SearchLocalManga($query, $page, $limit);
|
||||
$response = $this->handler->handle($searchQuery);
|
||||
|
||||
return new MangaCollection(
|
||||
items: array_map(
|
||||
fn ($item) => new MangaListItem(
|
||||
id: $item->id,
|
||||
title: $item->title,
|
||||
description: $item->description,
|
||||
slug: $item->slug,
|
||||
imageUrl: $item->imageUrl,
|
||||
thumbnailUrl: $item->thumbnailUrl,
|
||||
author: $item->author,
|
||||
publicationYear: $item->publicationYear,
|
||||
genres: $item->genres,
|
||||
status: $item->status,
|
||||
rating: $item->rating,
|
||||
createdAt: new \DateTimeImmutable()
|
||||
),
|
||||
$response->mangas
|
||||
),
|
||||
total: $response->total,
|
||||
page: $response->page,
|
||||
limit: $response->limit,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Domain\Manga\Infrastructure\Persistence;
|
||||
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
||||
use App\Domain\Manga\Domain\Model\Manga as DomainManga;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\ImageUrls;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
||||
@@ -24,7 +25,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
|
||||
{
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select('m')
|
||||
->from(EntityManga::class, 'm')
|
||||
@@ -50,7 +51,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
public function findById(string $id): ?DomainManga
|
||||
{
|
||||
$entity = $this->entityManager->find(EntityManga::class, $id);
|
||||
|
||||
|
||||
return $entity ? $this->toDomain($entity) : null;
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
$imageUrls = $manga->getImageUrls();
|
||||
$fullImageUrl = $imageUrls?->getFull();
|
||||
$thumbnailUrl = $imageUrls?->getThumbnail();
|
||||
|
||||
|
||||
$entity->setTitle($manga->getTitle()->getValue())
|
||||
->setSlug($manga->getSlug()->getValue())
|
||||
->setDescription($manga->getDescription())
|
||||
@@ -98,7 +99,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
public function delete(DomainManga $manga): void
|
||||
{
|
||||
$entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue());
|
||||
|
||||
|
||||
if ($entity) {
|
||||
$this->entityManager->remove($entity);
|
||||
$this->entityManager->flush();
|
||||
@@ -108,7 +109,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
public function findChapters(string $mangaId, int $page = 1, int $limit = 20, string $sortOrder = 'desc'): array
|
||||
{
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select('c')
|
||||
->from(EntityChapter::class, 'c')
|
||||
@@ -140,14 +141,14 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
$entity = $this->entityManager->getRepository(EntityManga::class)->findOneBy([
|
||||
'externalId' => $externalId->getValue()
|
||||
]);
|
||||
|
||||
|
||||
return $entity ? $this->toDomain($entity) : null;
|
||||
}
|
||||
|
||||
public function saveChapter(Chapter $chapter): void
|
||||
{
|
||||
$manga = $this->entityManager->find(EntityManga::class, $chapter->getMangaId());
|
||||
|
||||
|
||||
if (!$manga) {
|
||||
throw new \RuntimeException('Manga not found');
|
||||
}
|
||||
@@ -163,6 +164,42 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function search(string $query, int $page = 1, int $limit = 20): array
|
||||
{
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select('m')
|
||||
->from(EntityManga::class, 'm')
|
||||
->where('m.title LIKE :query')
|
||||
->orWhere('m.slug LIKE :query')
|
||||
// ->orWhere('m.author LIKE :query')
|
||||
// ->orWhere('m.description LIKE :query')
|
||||
->setParameter('query', '%' . $query . '%')
|
||||
->orderBy('m.title', 'ASC')
|
||||
->setFirstResult($offset)
|
||||
->setMaxResults($limit);
|
||||
|
||||
return array_map(
|
||||
fn (EntityManga $entity) => $this->toDomain($entity),
|
||||
$queryBuilder->getQuery()->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
public function countSearch(string $query): int
|
||||
{
|
||||
return $this->entityManager->createQueryBuilder()
|
||||
->select('COUNT(m.id)')
|
||||
->from(EntityManga::class, 'm')
|
||||
->where('m.title LIKE :query')
|
||||
->orWhere('m.slug LIKE :query')
|
||||
->orWhere('m.author LIKE :query')
|
||||
->orWhere('m.description LIKE :query')
|
||||
->setParameter('query', '%' . $query . '%')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
private function toDomain(EntityManga $entity): DomainManga
|
||||
{
|
||||
return new DomainManga(
|
||||
@@ -177,6 +214,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
externalId: $entity->getExternalId() ? new ExternalId($entity->getExternalId()) : null,
|
||||
imageUrl: $entity->getImageUrl(),
|
||||
rating: $entity->getRating(),
|
||||
imageUrls: $entity->getImageUrl() ? new ImageUrls($entity->getImageUrl(), $entity->getThumbnailUrl()) : null,
|
||||
createdAt: $entity->getCreatedAt(),
|
||||
);
|
||||
}
|
||||
@@ -184,13 +222,13 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
private function toChapterDomain(EntityChapter $entity): Chapter
|
||||
{
|
||||
return new Chapter(
|
||||
id: new ChapterId((string)$entity->getId()),
|
||||
mangaId: $entity->getManga()->getId(),
|
||||
number: $entity->getNumber(),
|
||||
title: $entity->getTitle(),
|
||||
volume: $entity->getVolume(),
|
||||
isVisible: $entity->isVisible(),
|
||||
createdAt: new \DateTimeImmutable()
|
||||
new ChapterId((string) $entity->getId()),
|
||||
$entity->getManga()->getId(),
|
||||
$entity->getNumber(),
|
||||
$entity->getTitle(),
|
||||
$entity->getVolume(),
|
||||
$entity->isVisible(),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user