Merged master

This commit is contained in:
Vincent
2024-09-18 13:37:07 +02:00
139 changed files with 8418 additions and 2409 deletions

View File

@@ -0,0 +1,101 @@
<?php
namespace App\MessageHandler;
use App\Entity\Domain;
use App\Entity\WatchList;
use App\Message\OrderDomain;
use App\Notifier\DomainOrderErrorNotification;
use App\Notifier\DomainOrderNotification;
use App\Repository\DomainRepository;
use App\Repository\WatchListRepository;
use App\Service\ChatNotificationService;
use App\Service\Connector\AbstractProvider;
use App\Service\StatService;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Mime\Address;
use Symfony\Component\Notifier\Recipient\Recipient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsMessageHandler]
final readonly class OrderDomainHandler
{
private Address $sender;
public function __construct(
string $mailerSenderEmail,
string $mailerSenderName,
private WatchListRepository $watchListRepository,
private DomainRepository $domainRepository,
private KernelInterface $kernel,
private HttpClientInterface $client,
private CacheItemPoolInterface $cacheItemPool,
private MailerInterface $mailer,
private LoggerInterface $logger,
private StatService $statService,
private ChatNotificationService $chatNotificationService,
#[Autowire(service: 'service_container')]
private ContainerInterface $locator
) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
}
/**
* @throws TransportExceptionInterface
* @throws \Symfony\Component\Notifier\Exception\TransportExceptionInterface
* @throws \Throwable
*/
public function __invoke(OrderDomain $message): void
{
/** @var WatchList $watchList */
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
/** @var Domain $domain */
$domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]);
$connector = $watchList->getConnector();
if (null !== $connector && $domain->getDeleted()) {
$this->logger->notice('Watchlist {watchlist} is linked to connector {connector}. A purchase attempt will be made for domain name {ldhName} with provider {provider}.', [
'watchlist' => $message->watchListToken,
'connector' => $connector->getId(),
'ldhName' => $message->ldhName,
'provider' => $connector->getProvider()->value,
]);
try {
$provider = $connector->getProvider();
if (null === $provider) {
throw new \InvalidArgumentException('Provider not found');
}
$connectorProviderClass = $provider->getConnectorProvider();
/** @var AbstractProvider $connectorProvider */
$connectorProvider = $this->locator->get($connectorProviderClass);
$connectorProvider->authenticate($connector->getAuthData());
$connectorProvider->orderDomain($domain, $this->kernel->isDebug());
$this->statService->incrementStat('stats.domain.purchased');
$notification = (new DomainOrderNotification($this->sender, $domain, $connector));
$this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage());
$this->chatNotificationService->sendChatNotification($watchList, $notification);
} catch (\Throwable $exception) {
$this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$notification = (new DomainOrderErrorNotification($this->sender, $domain));
$this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage());
$this->chatNotificationService->sendChatNotification($watchList, $notification);
$this->statService->incrementStat('stats.domain.purchase.failed');
throw $exception;
}
}
}
}

View File

@@ -1,164 +0,0 @@
<?php
namespace App\MessageHandler;
use App\Config\TriggerAction;
use App\Entity\Connector;
use App\Entity\Domain;
use App\Entity\DomainEvent;
use App\Entity\User;
use App\Entity\WatchList;
use App\Entity\WatchListTrigger;
use App\Message\ProcessDomainTrigger;
use App\Repository\DomainRepository;
use App\Repository\WatchListRepository;
use App\Service\Connector\ConnectorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsMessageHandler]
final readonly class ProcessDomainTriggerHandler
{
public function __construct(
private string $mailerSenderEmail,
private string $mailerSenderName,
private MailerInterface $mailer,
private WatchListRepository $watchListRepository,
private DomainRepository $domainRepository,
private KernelInterface $kernel,
private LoggerInterface $logger,
private HttpClientInterface $client,
#[Autowire(service: 'service_container')]
private ContainerInterface $locator
) {
}
/**
* @throws TransportExceptionInterface
* @throws \Exception
*/
public function __invoke(ProcessDomainTrigger $message): void
{
/** @var WatchList $watchList */
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
/** @var Domain $domain */
$domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]);
$connector = $watchList->getConnector();
if (null !== $connector && $domain->getDeleted()) {
$this->logger->notice('Watchlist {watchlist} is linked to connector {connector}. A purchase attempt will be made for domain name {ldhName} with provider {provider}.', [
'watchlist' => $message->watchListToken,
'connector' => $connector->getId(),
'ldhName' => $message->ldhName,
'provider' => $connector->getProvider()->value,
]);
try {
$provider = $connector->getProvider();
if (null === $provider) {
throw new \Exception('Provider not found');
}
$connectorProviderClass = $provider->getConnectorProvider();
/** @var ConnectorInterface $connectorProvider */
$connectorProvider = $this->locator->get($connectorProviderClass);
$connectorProvider->authenticate($connector->getAuthData());
$connectorProvider->orderDomain($domain, /* $this->kernel->isDebug() */ false);
$this->sendEmailDomainOrdered($domain, $connector, $watchList->getUser());
} catch (\Throwable $t) {
dump($t);
$this->logger->error('Unable to complete purchase. An error message is sent to user {username}.', [
'username' => $watchList->getUser()->getUserIdentifier(),
'error' => $t,
]);
$this->sendEmailDomainOrderError($domain, $watchList->getUser());
}
}
/** @var DomainEvent $event */
foreach ($domain->getEvents()->filter(fn ($event) => $message->updatedAt < $event->getDate() && $event->getDate() < new \DateTime()) as $event) {
$watchListTriggers = $watchList->getWatchListTriggers()
->filter(fn ($trigger) => $trigger->getEvent() === $event->getAction());
/** @var WatchListTrigger $watchListTrigger */
foreach ($watchListTriggers->getIterator() as $watchListTrigger) {
$this->logger->info('Action {event} has been detected on the domain name {ldhName}. A notification is sent to user {username}.', [
'event' => $event->getAction(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
if (TriggerAction::SendEmail == $watchListTrigger->getAction()) {
$this->sendEmailDomainUpdated($event, $watchList->getUser());
}
}
}
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainOrdered(Domain $domain, Connector $connector, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->priority(Email::PRIORITY_HIGHEST)
->subject('A domain name has been ordered')
->htmlTemplate('emails/success/domain_ordered.html.twig')
->locale('en')
->context([
'domain' => $domain,
'provider' => $connector->getProvider()->value,
]);
$this->mailer->send($email);
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainOrderError(Domain $domain, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->subject('An error occurred while ordering a domain name')
->htmlTemplate('emails/errors/domain_order.html.twig')
->locale('en')
->context([
'domain' => $domain,
]);
$this->mailer->send($email);
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainUpdated(DomainEvent $domainEvent, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->priority(Email::PRIORITY_HIGHEST)
->subject('A domain name has been changed')
->htmlTemplate('emails/success/domain_updated.html.twig')
->locale('en')
->context([
'event' => $domainEvent,
]);
$this->mailer->send($email);
}
}

View File

@@ -1,90 +0,0 @@
<?php
namespace App\MessageHandler;
use App\Entity\Domain;
use App\Entity\User;
use App\Entity\WatchList;
use App\Message\ProcessDomainTrigger;
use App\Message\ProcessWatchListTrigger;
use App\Repository\WatchListRepository;
use App\Service\RDAPService;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Mime\Address;
#[AsMessageHandler]
final readonly class ProcessWatchListTriggerHandler
{
public function __construct(
private RDAPService $RDAPService,
private MailerInterface $mailer,
private string $mailerSenderEmail,
private string $mailerSenderName,
private MessageBusInterface $bus,
private WatchListRepository $watchListRepository,
private LoggerInterface $logger
) {
}
/**
* @throws TransportExceptionInterface
* @throws \Exception
* @throws ExceptionInterface
*/
public function __invoke(ProcessWatchListTrigger $message): void
{
/** @var WatchList $watchList */
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
$this->logger->info('Domain names from Watchlist {token} will be processed.', [
'token' => $message->watchListToken,
]);
/** @var Domain $domain */
foreach ($watchList->getDomains()
->filter(fn ($domain) => $domain->getUpdatedAt()
->diff(
new \DateTimeImmutable('now'))->days >= 7
|| $this->RDAPService::isToBeWatchClosely($domain, $domain->getUpdatedAt())
) as $domain
) {
$updatedAt = $domain->getUpdatedAt();
try {
$this->RDAPService->registerDomain($domain->getLdhName());
} catch (\Throwable $e) {
$this->logger->error('An update error email is sent to user {username}.', [
'username' => $watchList->getUser()->getUserIdentifier(),
'error' => $e,
]);
$this->sendEmailDomainUpdateError($domain, $watchList->getUser());
}
$this->bus->dispatch(new ProcessDomainTrigger($watchList->getToken(), $domain->getLdhName(), $updatedAt));
}
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainUpdateError(Domain $domain, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->subject('An error occurred while updating a domain name')
->htmlTemplate('emails/errors/domain_update.html.twig')
->locale('en')
->context([
'domain' => $domain,
]);
$this->mailer->send($email);
}
}

View File

@@ -4,7 +4,7 @@ namespace App\MessageHandler;
use App\Entity\WatchList;
use App\Message\ProcessWatchListsTrigger;
use App\Message\ProcessWatchListTrigger;
use App\Message\UpdateDomainsFromWatchlist;
use App\Repository\WatchListRepository;
use Random\Randomizer;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
@@ -30,7 +30,7 @@ final readonly class ProcessWatchListsTriggerHandler
/** @var WatchList $watchList */
foreach ($watchLists as $watchList) {
$this->bus->dispatch(new ProcessWatchListTrigger($watchList->getToken()));
$this->bus->dispatch(new UpdateDomainsFromWatchlist($watchList->getToken()));
}
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\MessageHandler;
use App\Config\TriggerAction;
use App\Entity\Domain;
use App\Entity\DomainEvent;
use App\Entity\WatchList;
use App\Entity\WatchListTrigger;
use App\Message\SendDomainEventNotif;
use App\Notifier\DomainUpdateNotification;
use App\Repository\DomainRepository;
use App\Repository\WatchListRepository;
use App\Service\ChatNotificationService;
use App\Service\StatService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Notifier\Recipient\Recipient;
#[AsMessageHandler]
final readonly class SendDomainEventNotifHandler
{
private Address $sender;
public function __construct(
string $mailerSenderEmail,
string $mailerSenderName,
private LoggerInterface $logger,
private MailerInterface $mailer,
private StatService $statService,
private DomainRepository $domainRepository,
private WatchListRepository $watchListRepository,
private ChatNotificationService $chatNotificationService
) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
}
/**
* @throws TransportExceptionInterface
* @throws \Exception
* @throws ExceptionInterface
*/
public function __invoke(SendDomainEventNotif $message): void
{
/** @var WatchList $watchList */
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
/** @var Domain $domain */
$domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]);
/** @var DomainEvent $event */
foreach ($domain->getEvents()->filter(fn ($event) => $message->updatedAt < $event->getDate() && $event->getDate() < new \DateTime()) as $event) {
$watchListTriggers = $watchList->getWatchListTriggers()
->filter(fn ($trigger) => $trigger->getEvent() === $event->getAction());
/** @var WatchListTrigger $watchListTrigger */
foreach ($watchListTriggers->getIterator() as $watchListTrigger) {
$this->logger->info('Action {event} has been detected on the domain name {ldhName}. A notification is sent to user {username}.', [
'event' => $event->getAction(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$recipient = new Recipient($watchList->getUser()->getEmail());
$notification = new DomainUpdateNotification($this->sender, $event);
if (TriggerAction::SendEmail == $watchListTrigger->getAction()) {
$this->mailer->send($notification->asEmailMessage($recipient)->getMessage());
} elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) {
$this->chatNotificationService->sendChatNotification($watchList, $notification);
}
$this->statService->incrementStat('stats.alert.sent');
}
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace App\MessageHandler;
use App\Entity\Domain;
use App\Entity\WatchList;
use App\Message\OrderDomain;
use App\Message\SendDomainEventNotif;
use App\Message\UpdateDomainsFromWatchlist;
use App\Notifier\DomainUpdateErrorNotification;
use App\Repository\WatchListRepository;
use App\Service\RDAPService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Notifier\Recipient\Recipient;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
#[AsMessageHandler]
final readonly class UpdateDomainsFromWatchlistHandler
{
private Address $sender;
public function __construct(
private RDAPService $RDAPService,
private MailerInterface $mailer,
string $mailerSenderEmail,
string $mailerSenderName,
private MessageBusInterface $bus,
private WatchListRepository $watchListRepository,
private LoggerInterface $logger
) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
}
/**
* @throws ExceptionInterface
* @throws TransportExceptionInterface
* @throws ClientExceptionInterface
* @throws DecodingExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
* @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
* @throws \Throwable
*/
public function __invoke(UpdateDomainsFromWatchlist $message): void
{
/** @var WatchList $watchList */
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
$this->logger->info('Domain names from Watchlist {token} will be processed.', [
'token' => $message->watchListToken,
]);
/** @var Domain $domain */
foreach ($watchList->getDomains()
->filter(fn ($domain) => $domain->getUpdatedAt()
->diff(new \DateTimeImmutable())->days >= 7
|| (
($domain->getUpdatedAt()
->diff(new \DateTimeImmutable())->h * 60 + $domain->getUpdatedAt()
->diff(new \DateTimeImmutable())->i) >= 50
&& $this->RDAPService::isToBeWatchClosely($domain)
)
|| (count(array_intersect($domain->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
&& $domain->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1
)
) as $domain
) {
$updatedAt = $domain->getUpdatedAt();
try {
$this->RDAPService->registerDomain($domain->getLdhName());
$this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt));
} catch (NotFoundHttpException) {
if (null !== $watchList->getConnector()) {
$this->bus->dispatch(new OrderDomain($watchList->getToken(), $domain->getLdhName(), $updatedAt));
}
} catch (\Throwable $e) {
$this->logger->error('An update error email is sent to user {username}.', [
'username' => $watchList->getUser()->getUserIdentifier(),
'error' => $e,
]);
$email = (new DomainUpdateErrorNotification($this->sender, $domain))
->asEmailMessage(new Recipient($watchList->getUser()->getEmail()));
$this->mailer->send($email->getMessage());
throw $e;
}
}
}
}

View File

@@ -4,6 +4,7 @@ namespace App\MessageHandler;
use App\Message\UpdateRdapServers;
use App\Service\RDAPService;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
@@ -14,8 +15,10 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
#[AsMessageHandler]
final readonly class UpdateRdapServersHandler
{
public function __construct(private RDAPService $RDAPService)
{
public function __construct(
private RDAPService $RDAPService,
private ParameterBagInterface $bag
) {
}
/**
@@ -29,17 +32,32 @@ final readonly class UpdateRdapServersHandler
{
/** @var \Throwable[] $throws */
$throws = [];
try {
$this->RDAPService->updateTldListIANA();
$this->RDAPService->updateGTldListICANN();
} catch (\Throwable $throwable) {
$throws[] = $throwable;
}
try {
$this->RDAPService->updateRDAPServers();
$this->RDAPService->updateRDAPServersFromIANA();
} catch (\Throwable $throwable) {
$throws[] = $throwable;
}
try {
$this->RDAPService->updateRDAPServersFromIANA();
} catch (\Throwable $throwable) {
$throws[] = $throwable;
}
try {
$this->RDAPService->updateRDAPServersFromFile($this->bag->get('custom_rdap_servers_file'));
} catch (\Throwable $throwable) {
$throws[] = $throwable;
}
if (!empty($throws)) {
throw $throws[0];
}