feat: passer les logs et le sync Meilisearch en asynchrone via Messenger

src/Message/AppLogMessage.php (nouveau):
- Message serialisable avec method, url, route, action, userId (int
  nullable au lieu de l'entite User), ip
- Dispatche via le bus Messenger pour traitement asynchrone par Redis

src/MessageHandler/AppLogMessageHandler.php (nouveau):
- Recharge le User par ID depuis le repository
- Cree l'AppLog avec le HMAC et persiste en BDD
- Execute en arriere-plan sans bloquer la requete HTTP

src/Service/AppLoggerService.php:
- log(): dispatch maintenant un AppLogMessage via le bus Messenger
  au lieu de persister directement (asynchrone)
- logDirect(): reste synchrone pour les suppressions de logs qui
  doivent etre tracees immediatement avant la reponse HTTP
- Injection de MessageBusInterface en plus de EntityManager

src/Message/MeilisearchSyncMessage.php (nouveau):
- Message avec type (customer/revendeur/price), entityId et action
  (index ou remove)
- Constantes TYPE_CUSTOMER, TYPE_REVENDEUR, TYPE_PRICE

src/MessageHandler/MeilisearchSyncMessageHandler.php (nouveau):
- Recharge l'entite par ID selon le type
- Appelle indexCustomer/indexRevendeur/indexPrice ou les methodes
  remove correspondantes sur MeilisearchService
- Execute en arriere-plan via Redis

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-02 23:26:30 +02:00
parent 33bd89e617
commit 63c558e955
5 changed files with 157 additions and 6 deletions

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Message;
class AppLogMessage
{
public function __construct(
public readonly string $method,
public readonly string $url,
public readonly string $route,
public readonly string $action,
public readonly ?int $userId,
public readonly ?string $ip,
) {
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Message;
class MeilisearchSyncMessage
{
public const TYPE_CUSTOMER = 'customer';
public const TYPE_REVENDEUR = 'revendeur';
public const TYPE_PRICE = 'price';
public function __construct(
public readonly string $type,
public readonly int $entityId,
public readonly string $action = 'index',
) {
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\MessageHandler;
use App\Entity\AppLog;
use App\Message\AppLogMessage;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class AppLogMessageHandler
{
public function __construct(
private EntityManagerInterface $em,
private UserRepository $userRepository,
#[Autowire('%env(APP_SECRET)%')] private string $hmacSecret,
) {
}
public function __invoke(AppLogMessage $message): void
{
$user = null !== $message->userId ? $this->userRepository->find($message->userId) : null;
$log = new AppLog(
$message->method,
$message->url,
$message->route,
$message->action,
$this->hmacSecret,
$user,
$message->ip,
);
$this->em->persist($log);
$this->em->flush();
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\MessageHandler;
use App\Message\MeilisearchSyncMessage;
use App\Repository\CustomerRepository;
use App\Repository\PriceAutomaticRepository;
use App\Repository\RevendeurRepository;
use App\Service\MeilisearchService;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class MeilisearchSyncMessageHandler
{
public function __construct(
private MeilisearchService $meilisearch,
private CustomerRepository $customerRepository,
private RevendeurRepository $revendeurRepository,
private PriceAutomaticRepository $priceRepository,
) {
}
public function __invoke(MeilisearchSyncMessage $message): void
{
if ('remove' === $message->action) {
match ($message->type) {
MeilisearchSyncMessage::TYPE_CUSTOMER => $this->meilisearch->removeCustomer($message->entityId),
MeilisearchSyncMessage::TYPE_REVENDEUR => $this->meilisearch->removeRevendeur($message->entityId),
MeilisearchSyncMessage::TYPE_PRICE => $this->meilisearch->removePrice($message->entityId),
default => null,
};
return;
}
match ($message->type) {
MeilisearchSyncMessage::TYPE_CUSTOMER => $this->indexCustomer($message->entityId),
MeilisearchSyncMessage::TYPE_REVENDEUR => $this->indexRevendeur($message->entityId),
MeilisearchSyncMessage::TYPE_PRICE => $this->indexPrice($message->entityId),
default => null,
};
}
private function indexCustomer(int $id): void
{
$customer = $this->customerRepository->find($id);
if (null !== $customer) {
$this->meilisearch->indexCustomer($customer);
}
}
private function indexRevendeur(int $id): void
{
$revendeur = $this->revendeurRepository->find($id);
if (null !== $revendeur) {
$this->meilisearch->indexRevendeur($revendeur);
}
}
private function indexPrice(int $id): void
{
$price = $this->priceRepository->find($id);
if (null !== $price) {
$this->meilisearch->indexPrice($price);
}
}
}

View File

@@ -4,12 +4,14 @@ namespace App\Service;
use App\Entity\AppLog;
use App\Entity\User;
use App\Message\AppLogMessage;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\MessageBusInterface;
class AppLoggerService
{
/** @var array<string, string> Routes admin → descriptions lisibles */
/** @var array<string, string> */
private const ROUTE_LABELS = [
'app_admin_dashboard' => 'Consultation du tableau de bord',
'app_admin_clients_index' => 'Consultation de la liste des clients',
@@ -44,14 +46,20 @@ class AppLoggerService
'app_admin_tarification_edit' => 'Modification d\'un tarif',
'app_admin_services_index' => 'Consultation des services',
'app_admin_logs' => 'Consultation des logs',
'app_admin_logs_purge' => 'Suppression de tous les logs',
'app_admin_logs_delete' => 'Suppression d\'un log',
];
public function __construct(
private MessageBusInterface $bus,
private EntityManagerInterface $em,
#[Autowire('%env(APP_SECRET)%')] private string $hmacSecret,
) {
}
/**
* Log asynchrone via Messenger.
*/
public function log(string $method, string $url, string $route, ?User $user = null, ?string $ip = null): void
{
$action = self::ROUTE_LABELS[$route] ?? 'Acces a '.$route;
@@ -60,14 +68,18 @@ class AppLoggerService
$action .= ' (soumission)';
}
$log = new AppLog($method, $url, $route, $action, $this->hmacSecret, $user, $ip);
$this->em->persist($log);
$this->em->flush();
$this->bus->dispatch(new AppLogMessage(
$method,
$url,
$route,
$action,
$user?->getId(),
$ip,
));
}
/**
* Log direct avec action personnalisee (pour les suppressions de logs).
* Log synchrone direct (pour les suppressions qui doivent etre tracees immediatement).
*/
public function logDirect(string $method, string $url, string $route, string $action, ?User $user = null, ?string $ip = null): void
{