feat(sync) : add ModelTypeSyncService orchestrator and controller with tests

Implement the sync orchestrator that delegates to tagged strategies via
AutowireIterator, and the HTTP controller exposing sync-preview and sync
endpoints with transaction wrapping and role-based access control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-13 14:17:57 +01:00
parent 089ca43404
commit 4072abf7ba
4 changed files with 389 additions and 4 deletions

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use App\DTO\SyncConfirmation;
use App\Repository\ModelTypeRepository;
use App\Service\ModelTypeSyncService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/model_types/{id}')]
final class ModelTypeSyncController extends AbstractController
{
public function __construct(
private readonly ModelTypeRepository $modelTypes,
private readonly ModelTypeSyncService $syncService,
private readonly EntityManagerInterface $em,
) {}
#[Route('/sync-preview', name: 'api_model_type_sync_preview', methods: ['POST'])]
public function preview(string $id, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
$modelType = $this->modelTypes->find($id);
if (!$modelType) {
return new JsonResponse(
['message' => 'Catégorie introuvable.'],
Response::HTTP_NOT_FOUND,
);
}
$body = json_decode($request->getContent(), true);
$structure = $body['structure'] ?? [];
$result = $this->syncService->preview($modelType, $structure);
return new JsonResponse($result);
}
#[Route('/sync', name: 'api_model_type_sync', methods: ['POST'])]
public function sync(string $id, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
$modelType = $this->modelTypes->find($id);
if (!$modelType) {
return new JsonResponse(
['message' => 'Catégorie introuvable.'],
Response::HTTP_NOT_FOUND,
);
}
$body = json_decode($request->getContent(), true);
$confirmation = new SyncConfirmation(
confirmDeletions: $body['confirmDeletions'] ?? false,
confirmTypeChanges: $body['confirmTypeChanges'] ?? false,
);
$result = $this->em->wrapInTransaction(function () use ($modelType, $confirmation) {
return $this->syncService->execute($modelType, $confirmation);
});
return new JsonResponse($result);
}
}