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:
ext.jeremy.guillot@maxicoffee.domains
2025-06-30 20:00:09 +02:00
parent 896c57ac34
commit 9255509042
20 changed files with 1185 additions and 11 deletions

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -30,7 +30,8 @@ readonly class GetMangaBySlugStateProvider implements ProviderInterface
status: $response->status,
externalId: $response->externalId,
imageUrl: $response->imageUrl,
thumbnailUrl: $response->thumbnailUrl,
rating: $response->rating
);
}
}
}

View File

@@ -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,

View File

@@ -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(),
);
}