feat: SearchManga endpoint + tests

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-02-10 21:33:34 +01:00
parent 6667cc224b
commit ae0eac3197
25 changed files with 1022 additions and 10 deletions

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;
readonly class MangaSearchCollection
{
public function __construct(
/** @var MangaSearchItem[] */
public array $items
) {}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;
use ApiPlatform\Metadata\ApiProperty;
readonly class MangaSearchItem
{
public function __construct(
#[ApiProperty(identifier: true)]
public string $externalId,
public string $title,
public string $slug,
public string $description,
public string $author,
public int $publicationYear,
public array $genres,
public string $status,
public ?string $imageUrl,
public ?float $rating
) {}
}

View File

@@ -13,7 +13,51 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaChaptersS
new Get(
uriTemplate: '/mangas/{id}/chapters',
provider: GetMangaChaptersStateProvider::class,
output: ChapterCollection::class
output: ChapterCollection::class,
openapiContext: [
'parameters' => [
[
'name' => 'id',
'in' => 'path',
'required' => true,
'schema' => [
'type' => 'string'
],
'description' => 'The manga identifier'
],
[
'name' => 'page',
'in' => 'query',
'required' => false,
'schema' => [
'type' => 'integer',
'default' => 1
],
'description' => 'The page number'
],
[
'name' => 'limit',
'in' => 'query',
'required' => false,
'schema' => [
'type' => 'integer',
'default' => 20
],
'description' => 'Number of items per page'
],
[
'name' => 'sortOrder',
'in' => 'query',
'required' => false,
'schema' => [
'type' => 'string',
'enum' => ['asc', 'desc'],
'default' => 'desc'
],
'description' => 'Sort order for chapters'
]
]
]
)
]
)]

View File

@@ -13,7 +13,20 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProv
new Get(
uriTemplate: '/mangas/{id}',
provider: GetMangaStateProvider::class,
output: MangaDetail::class
output: MangaDetail::class,
openapiContext: [
'parameters' => [
[
'name' => 'id',
'in' => 'path',
'required' => true,
'schema' => [
'type' => 'string'
],
'description' => 'The manga identifier'
]
]
]
)
]
)]

View File

@@ -0,0 +1,35 @@
<?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\SearchMangaStateProvider;
#[ApiResource(
shortName: 'MangaSearch',
operations: [
new Get(
uriTemplate: '/mangas-search',
openapiContext: [
'parameters' => [
[
'name' => 'title',
'in' => 'query',
'required' => true,
'schema' => [
'type' => 'string'
],
'description' => 'The title to search for'
]
]
],
output: MangaSearchCollection::class,
provider: SearchMangaStateProvider::class
)
]
)]
class MangaSearchResource
{
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Domain\Manga\Application\Query\SearchManga;
use App\Domain\Manga\Application\QueryHandler\SearchMangaHandler;
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchCollection;
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchItem;
readonly class SearchMangaStateProvider implements ProviderInterface
{
public function __construct(
private SearchMangaHandler $handler
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): MangaSearchCollection
{
$title = $context['filters']['title'] ?? '';
$query = new SearchManga($title);
$response = $this->handler->handle($query);
return new MangaSearchCollection(
items: array_map(
fn ($item) => new MangaSearchItem(
externalId: $item->externalId,
title: $item->title,
slug: $item->slug,
description: $item->description,
author: $item->author,
publicationYear: $item->publicationYear,
genres: $item->genres,
status: $item->status,
imageUrl: $item->imageUrl,
rating: $item->rating
),
$response->items
)
);
}
}