feat: ajout de la fonctionnalité d'édition des mangas, incluant la création d'un modal d'édition, la mise à jour de l'API pour gérer les modifications, et l'intégration de la logique de gestion des erreurs. Tests ajoutés pour valider le bon fonctionnement de l'édition.
This commit is contained in:
parent
896c57ac34
commit
9255509042
@@ -11,6 +11,7 @@ readonly class MangaDetail
|
||||
public string $id,
|
||||
public string $title,
|
||||
public string $slug,
|
||||
public array $alternativeSlugs,
|
||||
public string $description,
|
||||
public string $author,
|
||||
public int $publicationYear,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\EditMangaProcessor;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Manga',
|
||||
operations: [
|
||||
new Put(
|
||||
uriTemplate: '/mangas/{id}/edit',
|
||||
processor: EditMangaProcessor::class,
|
||||
provider: GetMangaStateProvider::class,
|
||||
openapiContext: [
|
||||
'summary' => 'Edit an existing manga',
|
||||
'description' => 'Updates an existing manga with provided data (partial update supported)'
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class EditMangaResource
|
||||
{
|
||||
#[Assert\Length(min: 1, max: 255, minMessage: 'Le titre doit contenir au moins {{ limit }} caractère', maxMessage: 'Le titre ne peut pas dépasser {{ limit }} caractères')]
|
||||
public ?string $title = null;
|
||||
|
||||
public ?string $description = null;
|
||||
|
||||
public ?string $author = null;
|
||||
|
||||
#[Assert\Type(type: 'integer', message: 'L\'année de publication doit être un nombre entier')]
|
||||
#[Assert\Range(min: 1900, max: 2100, notInRangeMessage: 'L\'année de publication doit être comprise entre {{ min }} et {{ max }}')]
|
||||
public ?int $publicationYear = null;
|
||||
|
||||
#[Assert\Type(type: 'array', message: 'Les genres doivent être une liste')]
|
||||
#[Assert\Count(min: 1, minMessage: 'Vous devez spécifier au moins un genre')]
|
||||
public ?array $genres = null;
|
||||
|
||||
#[Assert\Choice(choices: ['ongoing', 'completed', 'hiatus'], message: 'Le statut doit être l\'un des suivants : ongoing, completed, hiatus')]
|
||||
public ?string $status = null;
|
||||
|
||||
#[Assert\Type(type: 'float', message: 'La note doit être un nombre décimal')]
|
||||
#[Assert\Range(min: 0, max: 10, notInRangeMessage: 'La note doit être comprise entre {{ min }} et {{ max }}')]
|
||||
public ?float $rating = null;
|
||||
|
||||
#[Assert\Type(type: 'array', message: 'Les slugs alternatifs doivent être une liste')]
|
||||
#[Assert\All([
|
||||
new Assert\Type('string'),
|
||||
new Assert\Regex(pattern: '/^[a-z0-9-]+$/', message: 'Chaque slug alternatif ne peut contenir que des lettres minuscules, des chiffres et des tirets')
|
||||
])]
|
||||
public ?array $alternativeSlugs = null;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Domain\Manga\Application\Command\EditManga;
|
||||
use App\Domain\Manga\Application\CommandHandler\EditMangaHandler;
|
||||
use App\Domain\Manga\Domain\Exception\MangaNotFoundException;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\EditMangaResource;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
readonly class EditMangaProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EditMangaHandler $handler
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
|
||||
{
|
||||
if (!$data instanceof EditMangaResource) {
|
||||
throw new \InvalidArgumentException('Invalid resource type');
|
||||
}
|
||||
|
||||
$mangaId = $uriVariables['id'] ?? null;
|
||||
if (!$mangaId) {
|
||||
throw new \InvalidArgumentException('Manga ID is required');
|
||||
}
|
||||
|
||||
$command = new EditManga(
|
||||
id: (string) $mangaId,
|
||||
title: $data->title,
|
||||
description: $data->description,
|
||||
author: $data->author,
|
||||
publicationYear: $data->publicationYear,
|
||||
genres: $data->genres,
|
||||
status: $data->status,
|
||||
rating: $data->rating,
|
||||
alternativeSlugs: $data->alternativeSlugs ?? []
|
||||
);
|
||||
|
||||
try {
|
||||
$this->handler->handle($command);
|
||||
} catch (MangaNotFoundException $e) {
|
||||
throw new NotFoundHttpException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ readonly class GetMangaBySlugStateProvider implements ProviderInterface
|
||||
status: $response->status,
|
||||
externalId: $response->externalId,
|
||||
imageUrl: $response->imageUrl,
|
||||
thumbnailUrl: $response->thumbnailUrl,
|
||||
rating: $response->rating
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ readonly class GetMangaStateProvider implements ProviderInterface
|
||||
id: $response->id,
|
||||
title: $response->title,
|
||||
slug: $response->slug,
|
||||
alternativeSlugs: $response->alternativeSlugs,
|
||||
description: $response->description,
|
||||
author: $response->author,
|
||||
publicationYear: $response->publicationYear,
|
||||
|
||||
@@ -63,9 +63,17 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
return $entity ? $this->toDomain($entity) : null;
|
||||
}
|
||||
|
||||
public function save(DomainManga $manga): void
|
||||
public function save(DomainManga $manga): void
|
||||
{
|
||||
$entity = new EntityManga();
|
||||
// Check if this is an update (manga has a numeric ID) or a new creation
|
||||
$entity = null;
|
||||
if ($manga->getId() && $manga->getId()->getValue() && is_numeric($manga->getId()->getValue())) {
|
||||
$entity = $this->entityManager->find(EntityManga::class, (int) $manga->getId()->getValue());
|
||||
}
|
||||
|
||||
if (!$entity) {
|
||||
$entity = new EntityManga();
|
||||
}
|
||||
|
||||
$imageUrls = $manga->getImageUrls();
|
||||
$fullImageUrl = $imageUrls?->getFull();
|
||||
@@ -78,10 +86,19 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
->setPublicationYear($manga->getPublicationYear())
|
||||
->setGenres($manga->getGenres())
|
||||
->setStatus($manga->getStatus())
|
||||
->setExternalId($manga->getExternalId()->getValue())
|
||||
->setImageUrl($fullImageUrl ?? null)
|
||||
->setThumbnailUrl($thumbnailUrl ?? null)
|
||||
->setMonitored(false);
|
||||
->setAlternativeSlugs($manga->getAlternativeSlugs());
|
||||
|
||||
// Only set externalId if it exists (to avoid setting null on update)
|
||||
if ($manga->getExternalId()) {
|
||||
$entity->setExternalId($manga->getExternalId()->getValue());
|
||||
}
|
||||
|
||||
// Only set monitored for new entities
|
||||
if (!$entity->getId()) {
|
||||
$entity->setMonitored(false);
|
||||
}
|
||||
|
||||
if ($manga->getRating() !== null) {
|
||||
$entity->setRating($manga->getRating());
|
||||
@@ -239,6 +256,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
|
||||
imageUrl: $entity->getImageUrl(),
|
||||
rating: $entity->getRating(),
|
||||
imageUrls: $entity->getImageUrl() ? new ImageUrls($entity->getImageUrl() ?? '', $entity->getThumbnailUrl() ?? '') : null,
|
||||
alternativeSlugs: $entity->getAlternativeSlugs() ?? [],
|
||||
createdAt: $entity->getCreatedAt(),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user