fix: lock domain purchase if already launched

This commit is contained in:
Maël Gangloff 2025-11-09 17:38:31 +01:00
parent 7f288c01e3
commit 66e2c25b18
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
5 changed files with 32 additions and 5 deletions

2
.env
View File

@ -42,7 +42,7 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###> symfony/lock ### ###> symfony/lock ###
# Choose one of the stores below # Choose one of the stores below
# postgresql+advisory://db_user:db_password@localhost/db_name # postgresql+advisory://db_user:db_password@localhost/db_name
LOCK_DSN=flock LOCK_DSN=redis://localhost:6379/lock?lazy=1
###< symfony/lock ### ###< symfony/lock ###
###> symfony/mailer ### ###> symfony/mailer ###

View File

@ -23,6 +23,7 @@
"php": ">=8.4", "php": ">=8.4",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"ext-redis": "*",
"ext-simplexml": "*", "ext-simplexml": "*",
"api-platform/core": "^3.3", "api-platform/core": "^3.3",
"doctrine/dbal": "^3", "doctrine/dbal": "^3",

3
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "1db291e0d108c6bb06d447134f1250c6", "content-hash": "97d6a4c9c86bc5a77dfdbc3e41e1deb5",
"packages": [ "packages": [
{ {
"name": "api-platform/core", "name": "api-platform/core",
@ -15394,6 +15394,7 @@
"php": ">=8.4", "php": ">=8.4",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"ext-redis": "*",
"ext-simplexml": "*" "ext-simplexml": "*"
}, },
"platform-dev": [], "platform-dev": [],

View File

@ -17,8 +17,11 @@ use App\Service\Provider\AbstractProvider;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler; use Symfony\Component\Messenger\Attribute\AsMessageHandler;
@ -43,7 +46,10 @@ final readonly class OrderDomainHandler
#[Autowire(service: 'service_container')] #[Autowire(service: 'service_container')]
private ContainerInterface $locator, private ContainerInterface $locator,
#[Autowire(param: 'influxdb_enabled')] #[Autowire(param: 'influxdb_enabled')]
private bool $influxdbEnabled, private EntityManagerInterface $em, private DomainPurchaseRepository $domainPurchaseRepository, private bool $influxdbEnabled, private EntityManagerInterface $em,
private DomainPurchaseRepository $domainPurchaseRepository,
#[Target('lock')]
private LockFactory $lockFactory,
) { ) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName); $this->sender = new Address($mailerSenderEmail, $mailerSenderName);
} }
@ -72,6 +78,23 @@ final readonly class OrderDomainHandler
return; return;
} }
$lock = $this->lockFactory->createLockFromKey(
new Key('domain_purchase.'.$domain->getLdhName().'.'.$connector->getId()),
ttl: 600,
autoRelease: false
);
if (!$lock->acquire()) {
$this->logger->notice('Purchase attempt is already launched for this domain name with this connector', [
'watchlist' => $message->watchlistToken,
'connector' => $connector->getId(),
'ldhName' => $message->ldhName,
'provider' => $connector->getProvider()->value,
]);
return;
}
$this->logger->notice('Watchlist is linked to a connector : a purchase attempt will be made for this domain name', [ $this->logger->notice('Watchlist is linked to a connector : a purchase attempt will be made for this domain name', [
'watchlist' => $message->watchlistToken, 'watchlist' => $message->watchlistToken,
'connector' => $connector->getId(), 'connector' => $connector->getId(),
@ -162,6 +185,8 @@ final readonly class OrderDomainHandler
$this->em->flush(); $this->em->flush();
} }
$lock->release();
throw $exception; throw $exception;
} }
} }

View File

@ -20,7 +20,7 @@ class DomainPurchaseRepository extends ServiceEntityRepository
{ {
return $this->createQueryBuilder('dp') return $this->createQueryBuilder('dp')
->select('COUNT(dp)') ->select('COUNT(dp)')
->where('dp.domainOrderedAt not NULL') ->where('dp.domainOrderedAt IS NOT NULL')
->getQuery()->getSingleScalarResult(); ->getQuery()->getSingleScalarResult();
} }
@ -28,7 +28,7 @@ class DomainPurchaseRepository extends ServiceEntityRepository
{ {
return $this->createQueryBuilder('dp') return $this->createQueryBuilder('dp')
->select('COUNT(dp)') ->select('COUNT(dp)')
->where('dp.domainOrderedAt is NULL') ->where('dp.domainOrderedAt IS NULL')
->getQuery()->getSingleScalarResult(); ->getQuery()->getSingleScalarResult();
} }
} }