feat: ajout d'une route GetMangaByIdHandler.php et fix de la SearchBar.vue
This commit is contained in:
parent
ed0a075a6c
commit
d9e935f7de
@@ -15,4 +15,4 @@ export class SearchMangas {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia';
|
import {defineStore} from 'pinia';
|
||||||
import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository';
|
import {ApiMangaRepository} from '../../infrastructure/api/apiMangaRepository';
|
||||||
|
|
||||||
const mangaRepository = new ApiMangaRepository();
|
const mangaRepository = new ApiMangaRepository();
|
||||||
|
|
||||||
@@ -19,32 +19,26 @@ export const useMangaStore = defineStore('manga', {
|
|||||||
// Actions pour la collection
|
// Actions pour la collection
|
||||||
async loadCollection() {
|
async loadCollection() {
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
|
|
||||||
console.log('Starting loadCollection...');
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Fetching collection from repository...');
|
|
||||||
this.collection = await mangaRepository.getCollection();
|
this.collection = await mangaRepository.getCollection();
|
||||||
console.log('Collection loaded:', this.collection);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.error = err.message;
|
this.error = err.message;
|
||||||
console.error('Failed to load collection:', err);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
console.log('loadCollection finished. Loading:', this.loading);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshCollectionInBackground() {
|
async refreshCollectionInBackground() {
|
||||||
if (this.isBackgroundLoading) return;
|
if (this.isBackgroundLoading) return;
|
||||||
|
|
||||||
this.isBackgroundLoading = true;
|
this.isBackgroundLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedCollection = await mangaRepository.getCollection();
|
this.collection = await mangaRepository.getCollection();
|
||||||
this.collection = updatedCollection;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to refresh collection:', err);
|
console.error('Failed to refresh collection:', err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -70,12 +64,10 @@ export const useMangaStore = defineStore('manga', {
|
|||||||
this.error = null;
|
this.error = null;
|
||||||
try {
|
try {
|
||||||
const response = await mangaRepository.getChapters(mangaId);
|
const response = await mangaRepository.getChapters(mangaId);
|
||||||
console.log('API Response:', response); // Pour déboguer
|
this.chapters = Array.isArray(response) ? response :
|
||||||
this.chapters = Array.isArray(response) ? response :
|
|
||||||
(response.items ? response.items : []);
|
(response.items ? response.items : []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error = error.message;
|
this.error = error.message;
|
||||||
console.error('Failed to fetch chapters:', error);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -86,4 +78,4 @@ export const useMangaStore = defineStore('manga', {
|
|||||||
this.chapters = [];
|
this.chapters = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export class Manga {
|
|||||||
description = null,
|
description = null,
|
||||||
authors = [],
|
authors = [],
|
||||||
imageUrl = null,
|
imageUrl = null,
|
||||||
|
thumbnailUrl = null,
|
||||||
publicationYear = null,
|
publicationYear = null,
|
||||||
status = null,
|
status = null,
|
||||||
rating = null,
|
rating = null,
|
||||||
@@ -18,6 +19,7 @@ export class Manga {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
this.authors = authors;
|
this.authors = authors;
|
||||||
this.imageUrl = imageUrl;
|
this.imageUrl = imageUrl;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
this.publicationYear = publicationYear;
|
this.publicationYear = publicationYear;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.rating = rating;
|
this.rating = rating;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class ApiMangaRepository {
|
|||||||
|
|
||||||
async getMangaById(id) {
|
async getMangaById(id) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/mangas/${id}`);
|
const response = await fetch(`/api/mangas/by-id/${id}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch manga details');
|
throw new Error('Failed to fetch manga details');
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ export class ApiMangaRepository {
|
|||||||
|
|
||||||
async getMangaBySlug(slug) {
|
async getMangaBySlug(slug) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/mangas/${slug}`);
|
const response = await fetch(`/api/mangas/by-slug/${slug}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch manga details');
|
throw new Error('Failed to fetch manga details');
|
||||||
}
|
}
|
||||||
@@ -87,4 +87,4 @@ export class ApiMangaRepository {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { storeToRefs } from 'pinia';
|
|||||||
import { useMangaStore } from '../../application/store/mangaStore';
|
import { useMangaStore } from '../../application/store/mangaStore';
|
||||||
import MangaGrid from '../components/MangaGrid.vue';
|
import MangaGrid from '../components/MangaGrid.vue';
|
||||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||||
import {
|
import {
|
||||||
ArrowPathIcon,
|
ArrowPathIcon,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
Cog6ToothIcon,
|
Cog6ToothIcon,
|
||||||
@@ -29,35 +29,22 @@ import {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const mangaStore = useMangaStore();
|
const mangaStore = useMangaStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
collection,
|
collection,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
isBackgroundLoading
|
isBackgroundLoading
|
||||||
} = storeToRefs(mangaStore);
|
} = storeToRefs(mangaStore);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('HomePage mounted');
|
|
||||||
console.log('Store state before loadCollection:', {
|
|
||||||
collection: collection.value,
|
|
||||||
loading: loading.value,
|
|
||||||
error: error.value
|
|
||||||
});
|
|
||||||
|
|
||||||
mangaStore.loadCollection();
|
mangaStore.loadCollection();
|
||||||
|
|
||||||
console.log('loadCollection called');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAddMangaClick = (query = '') => {
|
|
||||||
router.push(`/add${query ? `?q=${encodeURIComponent(query)}` : ''}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toolbarConfig = {
|
const toolbarConfig = {
|
||||||
leftSection: [
|
leftSection: [
|
||||||
{
|
{
|
||||||
icon: ArrowPathIcon,
|
icon: ArrowPathIcon,
|
||||||
label: 'Refresh',
|
label: 'Refresh',
|
||||||
onClick: () => mangaStore.refreshCollectionInBackground(),
|
onClick: () => mangaStore.refreshCollectionInBackground(),
|
||||||
active: isBackgroundLoading
|
active: isBackgroundLoading
|
||||||
},
|
},
|
||||||
@@ -70,4 +57,4 @@ const toolbarConfig = {
|
|||||||
{ icon: FunnelIcon, onClick: () => {} }
|
{ icon: FunnelIcon, onClick: () => {} }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useMangaStore } from '../../application/store/mangaStore';
|
import { useMangaStore } from '../../application/store/mangaStore';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -238,6 +238,13 @@ const loadData = async () => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ajouter le watcher sur l'ID de la route
|
||||||
|
watch(() => route.params.id, (newId, oldId) => {
|
||||||
|
if (newId !== oldId) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Actions sur les chapitres et volumes
|
// Actions sur les chapitres et volumes
|
||||||
const searchChapter = async (chapter) => {
|
const searchChapter = async (chapter) => {
|
||||||
// TODO: Implémenter la recherche de chapitre
|
// TODO: Implémenter la recherche de chapitre
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import Layout from '../shared/components/layout/Layout.vue';
|
import Layout from '../shared/components/layout/Layout.vue';
|
||||||
import HomePage from '../domain/manga/presentation/pages/HomePage.vue';
|
import HomePage from '../domain/manga/presentation/pages/HomePage.vue';
|
||||||
|
import MangaDetails from "../domain/manga/presentation/pages/MangaDetails.vue";
|
||||||
|
|
||||||
// Placeholder component for new routes
|
// Placeholder component for new routes
|
||||||
const PlaceholderComponent = {
|
const PlaceholderComponent = {
|
||||||
@@ -31,7 +32,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/manga/:id',
|
path: '/manga/:id',
|
||||||
name: 'manga-details',
|
name: 'manga-details',
|
||||||
component: () => import('../domain/manga/presentation/pages/MangaDetails.vue')
|
component: MangaDetails
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/add',
|
path: '/add',
|
||||||
@@ -133,4 +134,4 @@ const routes = [
|
|||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHistory('/vue/'),
|
history: createWebHistory('/vue/'),
|
||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
<button
|
<button
|
||||||
v-for="manga in results"
|
v-for="manga in results"
|
||||||
:key="manga.id"
|
:key="manga.id"
|
||||||
@click="handleMangaClick(manga.slug)"
|
@click="handleMangaClick(manga.id)"
|
||||||
class="w-full px-4 py-2 flex items-center gap-3 hover:bg-gray-700/50 text-white"
|
class="w-full px-4 py-2 flex items-center gap-3 hover:bg-gray-700/50 text-white"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="manga.imageUrl"
|
:src="manga.thumbnailUrl"
|
||||||
:alt="manga.title"
|
:alt="manga.title"
|
||||||
class="w-10 h-14 object-cover rounded"
|
class="w-10 h-14 object-cover rounded"
|
||||||
/>
|
/>
|
||||||
@@ -90,17 +90,20 @@ const searchManga = async () => {
|
|||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
results.value = await searchMangas.execute(query.value);
|
const response = await searchMangas.execute(query.value);
|
||||||
|
results.value = Array.isArray(response) ? response : response.items || [];
|
||||||
hasSearched.value = true;
|
hasSearched.value = true;
|
||||||
|
console.log('Résultats de recherche:', results.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Search error:', error);
|
console.error('Search error:', error);
|
||||||
|
results.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMangaClick = (slug) => {
|
const handleMangaClick = (id) => {
|
||||||
router.push(`/manga/${slug}`);
|
router.push(`/manga/${id}`);
|
||||||
isOpen.value = false;
|
isOpen.value = false;
|
||||||
query.value = '';
|
query.value = '';
|
||||||
hasSearched.value = false;
|
hasSearched.value = false;
|
||||||
|
|||||||
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(),
|
status: $manga->getStatus(),
|
||||||
externalId: $manga->getExternalId()?->getValue(),
|
externalId: $manga->getExternalId()?->getValue(),
|
||||||
imageUrl: $manga->getImageUrl(),
|
imageUrl: $manga->getImageUrl(),
|
||||||
|
thumbnailUrl: $manga->getImageUrls()->getThumbnail(),
|
||||||
rating: $manga->getRating()
|
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 $status,
|
||||||
public ?string $externalId,
|
public ?string $externalId,
|
||||||
public ?string $imageUrl,
|
public ?string $imageUrl,
|
||||||
|
public ?string $thumbnailUrl,
|
||||||
public ?float $rating
|
public ?float $rating
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Domain\Manga\Application\Response;
|
|||||||
readonly class MangaSearchItem
|
readonly class MangaSearchItem
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
public string $externalId,
|
public string $externalId,
|
||||||
public string $title,
|
public string $title,
|
||||||
public string $slug,
|
public string $slug,
|
||||||
@@ -14,6 +15,7 @@ readonly class MangaSearchItem
|
|||||||
public array $genres,
|
public array $genres,
|
||||||
public string $status,
|
public string $status,
|
||||||
public ?string $imageUrl,
|
public ?string $imageUrl,
|
||||||
|
public ?string $thumbnailUrl,
|
||||||
public ?float $rating
|
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 findByExternalId(ExternalId $externalId): ?Manga;
|
||||||
public function saveChapter(Chapter $chapter): void;
|
public function saveChapter(Chapter $chapter): void;
|
||||||
public function findBySlug(MangaSlug $slug): ?Manga;
|
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 $description,
|
||||||
public string $slug,
|
public string $slug,
|
||||||
public ?string $imageUrl,
|
public ?string $imageUrl,
|
||||||
|
public ?string $thumbnailUrl,
|
||||||
public string $author,
|
public string $author,
|
||||||
public int $publicationYear,
|
public int $publicationYear,
|
||||||
public array $genres,
|
public array $genres,
|
||||||
@@ -21,4 +22,4 @@ readonly class MangaListItem
|
|||||||
public ?float $rating,
|
public ?float $rating,
|
||||||
public DateTimeImmutable $createdAt,
|
public DateTimeImmutable $createdAt,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ readonly class MangaSearchItem
|
|||||||
public array $genres,
|
public array $genres,
|
||||||
public string $status,
|
public string $status,
|
||||||
public ?string $imageUrl,
|
public ?string $imageUrl,
|
||||||
|
public ?string $thumbnailUrl,
|
||||||
public ?float $rating
|
public ?float $rating
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProv
|
|||||||
shortName: 'Manga',
|
shortName: 'Manga',
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
uriTemplate: '/mangas/{id}',
|
uriTemplate: '/mangas/by-id/{id}',
|
||||||
provider: GetMangaStateProvider::class,
|
provider: GetMangaStateProvider::class,
|
||||||
output: MangaDetail::class,
|
output: MangaDetail::class,
|
||||||
openapiContext: [
|
openapiContext: [
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchCollection;
|
|||||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchMangaStateProvider;
|
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchMangaStateProvider;
|
||||||
|
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
shortName: 'Manga',
|
shortName: 'Mangadex',
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
uriTemplate: '/mangas-search',
|
uriTemplate: '/mangadex-search',
|
||||||
openapiContext: [
|
openapiContext: [
|
||||||
'parameters' => [
|
'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(
|
return new MangaListItem(
|
||||||
id: $manga->getId()->getValue(),
|
id: $manga->getId()->getValue(),
|
||||||
title: $manga->getTitle()->getValue(),
|
title: $manga->getTitle()->getValue(),
|
||||||
slug: $manga->getSlug()->getValue(),
|
|
||||||
description: $manga->getDescription(),
|
description: $manga->getDescription(),
|
||||||
|
slug: $manga->getSlug()->getValue(),
|
||||||
imageUrl: $manga->getImageUrl(),
|
imageUrl: $manga->getImageUrl(),
|
||||||
|
thumbnailUrl: $manga->getImageUrls()->getThumbnail(),
|
||||||
author: $manga->getAuthor(),
|
author: $manga->getAuthor(),
|
||||||
publicationYear: $manga->getPublicationYear(),
|
publicationYear: $manga->getPublicationYear(),
|
||||||
genres: $manga->getGenres(),
|
genres: $manga->getGenres(),
|
||||||
@@ -55,4 +56,4 @@ readonly class GetMangaListStateProvider implements ProviderInterface
|
|||||||
createdAt: $manga->getCreatedAt()
|
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\Contract\Repository\MangaRepositoryInterface;
|
||||||
use App\Domain\Manga\Domain\Model\Manga as DomainManga;
|
use App\Domain\Manga\Domain\Model\Manga as DomainManga;
|
||||||
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
|
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\MangaId;
|
||||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
|
||||||
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
|
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
|
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
|
||||||
{
|
{
|
||||||
$offset = ($page - 1) * $limit;
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||||
->select('m')
|
->select('m')
|
||||||
->from(EntityManga::class, 'm')
|
->from(EntityManga::class, 'm')
|
||||||
@@ -50,7 +51,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
public function findById(string $id): ?DomainManga
|
public function findById(string $id): ?DomainManga
|
||||||
{
|
{
|
||||||
$entity = $this->entityManager->find(EntityManga::class, $id);
|
$entity = $this->entityManager->find(EntityManga::class, $id);
|
||||||
|
|
||||||
return $entity ? $this->toDomain($entity) : null;
|
return $entity ? $this->toDomain($entity) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
$imageUrls = $manga->getImageUrls();
|
$imageUrls = $manga->getImageUrls();
|
||||||
$fullImageUrl = $imageUrls?->getFull();
|
$fullImageUrl = $imageUrls?->getFull();
|
||||||
$thumbnailUrl = $imageUrls?->getThumbnail();
|
$thumbnailUrl = $imageUrls?->getThumbnail();
|
||||||
|
|
||||||
$entity->setTitle($manga->getTitle()->getValue())
|
$entity->setTitle($manga->getTitle()->getValue())
|
||||||
->setSlug($manga->getSlug()->getValue())
|
->setSlug($manga->getSlug()->getValue())
|
||||||
->setDescription($manga->getDescription())
|
->setDescription($manga->getDescription())
|
||||||
@@ -98,7 +99,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
public function delete(DomainManga $manga): void
|
public function delete(DomainManga $manga): void
|
||||||
{
|
{
|
||||||
$entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue());
|
$entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue());
|
||||||
|
|
||||||
if ($entity) {
|
if ($entity) {
|
||||||
$this->entityManager->remove($entity);
|
$this->entityManager->remove($entity);
|
||||||
$this->entityManager->flush();
|
$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
|
public function findChapters(string $mangaId, int $page = 1, int $limit = 20, string $sortOrder = 'desc'): array
|
||||||
{
|
{
|
||||||
$offset = ($page - 1) * $limit;
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||||
->select('c')
|
->select('c')
|
||||||
->from(EntityChapter::class, 'c')
|
->from(EntityChapter::class, 'c')
|
||||||
@@ -140,14 +141,14 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
$entity = $this->entityManager->getRepository(EntityManga::class)->findOneBy([
|
$entity = $this->entityManager->getRepository(EntityManga::class)->findOneBy([
|
||||||
'externalId' => $externalId->getValue()
|
'externalId' => $externalId->getValue()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $entity ? $this->toDomain($entity) : null;
|
return $entity ? $this->toDomain($entity) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveChapter(Chapter $chapter): void
|
public function saveChapter(Chapter $chapter): void
|
||||||
{
|
{
|
||||||
$manga = $this->entityManager->find(EntityManga::class, $chapter->getMangaId());
|
$manga = $this->entityManager->find(EntityManga::class, $chapter->getMangaId());
|
||||||
|
|
||||||
if (!$manga) {
|
if (!$manga) {
|
||||||
throw new \RuntimeException('Manga not found');
|
throw new \RuntimeException('Manga not found');
|
||||||
}
|
}
|
||||||
@@ -163,6 +164,42 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
$this->entityManager->flush();
|
$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
|
private function toDomain(EntityManga $entity): DomainManga
|
||||||
{
|
{
|
||||||
return new DomainManga(
|
return new DomainManga(
|
||||||
@@ -177,6 +214,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
externalId: $entity->getExternalId() ? new ExternalId($entity->getExternalId()) : null,
|
externalId: $entity->getExternalId() ? new ExternalId($entity->getExternalId()) : null,
|
||||||
imageUrl: $entity->getImageUrl(),
|
imageUrl: $entity->getImageUrl(),
|
||||||
rating: $entity->getRating(),
|
rating: $entity->getRating(),
|
||||||
|
imageUrls: $entity->getImageUrl() ? new ImageUrls($entity->getImageUrl(), $entity->getThumbnailUrl()) : null,
|
||||||
createdAt: $entity->getCreatedAt(),
|
createdAt: $entity->getCreatedAt(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -184,13 +222,13 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
|||||||
private function toChapterDomain(EntityChapter $entity): Chapter
|
private function toChapterDomain(EntityChapter $entity): Chapter
|
||||||
{
|
{
|
||||||
return new Chapter(
|
return new Chapter(
|
||||||
id: new ChapterId((string)$entity->getId()),
|
new ChapterId((string) $entity->getId()),
|
||||||
mangaId: $entity->getManga()->getId(),
|
$entity->getManga()->getId(),
|
||||||
number: $entity->getNumber(),
|
$entity->getNumber(),
|
||||||
title: $entity->getTitle(),
|
$entity->getTitle(),
|
||||||
volume: $entity->getVolume(),
|
$entity->getVolume(),
|
||||||
isVisible: $entity->isVisible(),
|
$entity->isVisible(),
|
||||||
createdAt: new \DateTimeImmutable()
|
new \DateTimeImmutable()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,4 +148,35 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
|
|||||||
$this->chapters = [];
|
$this->chapters = [];
|
||||||
$this->savedChapters = [];
|
$this->savedChapters = [];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public function search(string $query, int $page = 1, int $limit = 20): array
|
||||||
|
{
|
||||||
|
$filteredMangas = array_filter($this->mangas, function (Manga $manga) use ($query) {
|
||||||
|
$searchableFields = [
|
||||||
|
$manga->getTitle()->getValue(),
|
||||||
|
$manga->getSlug()->getValue(),
|
||||||
|
$manga->getAuthor(),
|
||||||
|
$manga->getDescription()
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($searchableFields as $field) {
|
||||||
|
if (str_contains(strtolower($field), strtolower($query))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$sortedMangas = array_values($filteredMangas);
|
||||||
|
usort($sortedMangas, fn (Manga $a, Manga $b) => $a->getTitle()->getValue() <=> $b->getTitle()->getValue());
|
||||||
|
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
return array_slice($sortedMangas, $offset, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countSearch(string $query): int
|
||||||
|
{
|
||||||
|
return count($this->search($query, 1, PHP_INT_MAX));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ class GetMangaTest extends AbstractApiTestCase
|
|||||||
{
|
{
|
||||||
// When
|
// When
|
||||||
$client = static::createClient();
|
$client = static::createClient();
|
||||||
$response = $client->request('GET', '/api/mangas/999');
|
$response = $client->request('GET', '/api/mangas/by-id/999');
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
$this->assertResponseStatusCodeSame(404);
|
$this->assertResponseStatusCodeSame(404);
|
||||||
@@ -42,7 +42,7 @@ class GetMangaTest extends AbstractApiTestCase
|
|||||||
|
|
||||||
// When
|
// When
|
||||||
$client = static::createClient();
|
$client = static::createClient();
|
||||||
$response = $client->request('GET', '/api/mangas/' . $manga->getId());
|
$response = $client->request('GET', '/api/mangas/by-id/' . $manga->getId());
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
$this->assertResponseIsSuccessful();
|
$this->assertResponseIsSuccessful();
|
||||||
|
|||||||
139
tests/Feature/Manga/SearchMangaTest.php
Normal file
139
tests/Feature/Manga/SearchMangaTest.php
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Feature\Manga;
|
||||||
|
|
||||||
|
use App\Entity\Manga;
|
||||||
|
use App\Tests\Feature\AbstractApiTestCase;
|
||||||
|
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||||
|
|
||||||
|
class SearchMangaTest extends AbstractApiTestCase
|
||||||
|
{
|
||||||
|
use ResetDatabase;
|
||||||
|
|
||||||
|
public function testSearchMangaWithEmptyQuery(): void
|
||||||
|
{
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas/search', [
|
||||||
|
'query' => [
|
||||||
|
'q' => ''
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseStatusCodeSame(400);
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'hydra:title' => 'An error occurred',
|
||||||
|
'hydra:description' => 'Le terme de recherche doit contenir au moins 3 caractères'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSearchMangaWithShortQuery(): void
|
||||||
|
{
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas/search', [
|
||||||
|
'query' => [
|
||||||
|
'q' => 'on'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseStatusCodeSame(400);
|
||||||
|
$this->assertJsonContains([
|
||||||
|
'hydra:title' => 'An error occurred',
|
||||||
|
'hydra:description' => 'Le terme de recherche doit contenir au moins 3 caractères'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSearchMangaByTitle(): void
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
$this->createManga('One Piece', 'one-piece');
|
||||||
|
$this->createManga('Dragon Ball', 'dragon-ball');
|
||||||
|
$this->createManga('One Punch Man', 'one-punch-man');
|
||||||
|
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas/search', [
|
||||||
|
'query' => [
|
||||||
|
'q' => 'one'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$data = $response->toArray();
|
||||||
|
|
||||||
|
$this->assertCount(2, $data['items']);
|
||||||
|
$titles = array_map(fn($item) => $item['title'], $data['items']);
|
||||||
|
$this->assertContains('One Piece', $titles);
|
||||||
|
$this->assertContains('One Punch Man', $titles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSearchMangaBySlug(): void
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
$this->createManga('One Piece', 'one-piece');
|
||||||
|
$this->createManga('Dragon Ball', 'dragon-ball');
|
||||||
|
|
||||||
|
// When
|
||||||
|
$client = static::createClient();
|
||||||
|
$response = $client->request('GET', '/api/mangas/search', [
|
||||||
|
'query' => [
|
||||||
|
'q' => 'dragon'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$data = $response->toArray();
|
||||||
|
|
||||||
|
$this->assertCount(1, $data['items']);
|
||||||
|
$this->assertEquals('Dragon Ball', $data['items'][0]['title']);
|
||||||
|
$this->assertEquals('dragon-ball', $data['items'][0]['slug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function testSearchMangaWithPagination(): void
|
||||||
|
// {
|
||||||
|
// // Given
|
||||||
|
// $this->createManga('One Piece', 'one-piece');
|
||||||
|
// $this->createManga('One Punch Man', 'one-punch-man');
|
||||||
|
// $this->createManga('One Outs', 'one-outs');
|
||||||
|
|
||||||
|
// // When
|
||||||
|
// $client = static::createClient();
|
||||||
|
// $response = $client->request('GET', '/api/mangas/search', [
|
||||||
|
// 'query' => [
|
||||||
|
// 'q' => 'one',
|
||||||
|
// 'page' => 2,
|
||||||
|
// 'limit' => 1
|
||||||
|
// ]
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// // Then
|
||||||
|
// $this->assertResponseIsSuccessful();
|
||||||
|
// $data = $response->toArray();
|
||||||
|
|
||||||
|
// $this->assertCount(1, $data['items']);
|
||||||
|
// $this->assertTrue($data['hasNextPage']);
|
||||||
|
// $this->assertTrue($data['hasPreviousPage']);
|
||||||
|
// }
|
||||||
|
|
||||||
|
private function createManga(string $title, string $slug): void
|
||||||
|
{
|
||||||
|
$manga = new Manga();
|
||||||
|
$manga->setTitle($title)
|
||||||
|
->setSlug($slug)
|
||||||
|
->setDescription('Description test')
|
||||||
|
->setAuthor('Author test')
|
||||||
|
->setPublicationYear(2020)
|
||||||
|
->setGenres(['action'])
|
||||||
|
->setStatus('ongoing')
|
||||||
|
->setRating(4.5)
|
||||||
|
->setMonitored(false);
|
||||||
|
|
||||||
|
$this->entityManager->persist($manga);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user