From dc3879c4c525f857f09054e784faa612db2a0ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Tue, 3 Dec 2024 21:10:38 +0100 Subject: [PATCH] docs: add documentation on MessageHandler --- src/Controller/WatchListController.php | 2 +- src/MessageHandler/OrderDomainHandler.php | 89 +++++++++++------- .../ProcessWatchListsTriggerHandler.php | 5 ++ .../SendDomainEventNotifHandler.php | 8 ++ .../UpdateDomainsFromWatchlistHandler.php | 19 ++++ .../UpdateRdapServersHandler.php | 13 +++ src/Service/ChatNotificationService.php | 90 ++++++++++--------- 7 files changed, 151 insertions(+), 75 deletions(-) diff --git a/src/Controller/WatchListController.php b/src/Controller/WatchListController.php index 4a230b1..2f3b7d7 100644 --- a/src/Controller/WatchListController.php +++ b/src/Controller/WatchListController.php @@ -363,7 +363,7 @@ class WatchListController extends AbstractController } usort($domains, function (Domain $d1, Domain $d2) { - $IMPORTANT_STATUS = ['pending delete', 'redemption period', 'auto renew period']; + $IMPORTANT_STATUS = ['pending delete', 'redemption period']; /** @var \DateTimeImmutable $exp1 */ $exp1 = $d1->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())->getDate(); diff --git a/src/MessageHandler/OrderDomainHandler.php b/src/MessageHandler/OrderDomainHandler.php index 97bdae2..6aedc06 100644 --- a/src/MessageHandler/OrderDomainHandler.php +++ b/src/MessageHandler/OrderDomainHandler.php @@ -56,44 +56,73 @@ final readonly class OrderDomainHandler $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}.', [ + + /* + * We make sure that the domain name is marked absent from WHOIS in the database before continuing. + * A connector must also be associated with the Watchlist to allow the purchase of the domain name. + * Otherwise, we do nothing. + */ + + if (null === $connector || !$domain->getDeleted()) { + return; + } + + $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); + + /* + * The user is authenticated to ensure that the credentials are still valid. + * If no errors occur, the purchase is attempted. + */ + + $connectorProvider->authenticate($connector->getAuthData()); + + $connectorProvider->orderDomain($domain, $this->kernel->isDebug()); + + /* + * If the purchase was successful, the statistics are updated and a success message is sent to the user. + */ + $this->logger->notice('Watchlist {watchlist} is linked to connector {connector}. A purchase was successfully made for domain {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(); + $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) { + /* + * The purchase was not successful (for several possible reasons that we have not determined). + * The user is informed and the exception is raised, which may allow you to try again. + */ + $this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [ + 'username' => $watchList->getUser()->getUserIdentifier(), + ]); - /** @var AbstractProvider $connectorProvider */ - $connectorProvider = $this->locator->get($connectorProviderClass); - $connectorProvider->authenticate($connector->getAuthData()); + $this->statService->incrementStat('stats.domain.purchase.failed'); + $notification = (new DomainOrderErrorNotification($this->sender, $domain)); + $this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage()); + $this->chatNotificationService->sendChatNotification($watchList, $notification); - $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; - } + throw $exception; } } } diff --git a/src/MessageHandler/ProcessWatchListsTriggerHandler.php b/src/MessageHandler/ProcessWatchListsTriggerHandler.php index ac270b8..492bc72 100644 --- a/src/MessageHandler/ProcessWatchListsTriggerHandler.php +++ b/src/MessageHandler/ProcessWatchListsTriggerHandler.php @@ -25,6 +25,11 @@ final readonly class ProcessWatchListsTriggerHandler */ public function __invoke(ProcessWatchListsTrigger $message): void { + /* + * We shuffle the watch lists to process them in an order that we consider random. + * The shuffling is provided by a high-level API of a CSPRNG number generator. + */ + $randomizer = new Randomizer(); $watchLists = $randomizer->shuffleArray($this->watchListRepository->findAll()); diff --git a/src/MessageHandler/SendDomainEventNotifHandler.php b/src/MessageHandler/SendDomainEventNotifHandler.php index 363d8d1..cc4692e 100644 --- a/src/MessageHandler/SendDomainEventNotifHandler.php +++ b/src/MessageHandler/SendDomainEventNotifHandler.php @@ -51,11 +51,19 @@ final readonly class SendDomainEventNotifHandler /** @var Domain $domain */ $domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]); + /* + * For each new event whose date is after the domain name update date (before the current domain name update) + */ + /** @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()); + /* + * For each trigger, we perform the appropriate action: send email or send push notification (for now) + */ + /** @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}.', [ diff --git a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php index bc7069c..fe7ba4f 100644 --- a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php +++ b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php @@ -60,6 +60,13 @@ final readonly class UpdateDomainsFromWatchlistHandler 'token' => $message->watchListToken, ]); + /* + * A domain name is updated if one or more of these conditions are met: + * - was updated more than 7 days ago + * - has statuses that suggest it will expire soon AND was updated more than an hour ago + * - has specific statuses that suggest there is a dispute between the registrant and the registrar AND was updated more than a day ago + */ + /** @var Domain $domain */ foreach ($watchList->getDomains() ->filter(fn ($domain) => $domain->getUpdatedAt() @@ -78,13 +85,25 @@ final readonly class UpdateDomainsFromWatchlistHandler $updatedAt = $domain->getUpdatedAt(); try { + /* + * Domain name update + * We send messages that correspond to the sending of notifications that will not be processed here. + */ $this->RDAPService->registerDomain($domain->getLdhName()); $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); } catch (NotFoundHttpException) { if (null !== $watchList->getConnector()) { + /* + * If the domain name no longer appears in the WHOIS AND a connector is associated with this Watchlist, + * this connector is used to purchase the domain name. + */ $this->bus->dispatch(new OrderDomain($watchList->getToken(), $domain->getLdhName(), $updatedAt)); } } catch (\Throwable $e) { + /* + * In case of another unknown error, + * the owner of the Watchlist is informed that an error occurred in updating the domain name. + */ $this->logger->error('An update error email is sent to user {username}.', [ 'username' => $watchList->getUser()->getUserIdentifier(), 'error' => $e, diff --git a/src/MessageHandler/UpdateRdapServersHandler.php b/src/MessageHandler/UpdateRdapServersHandler.php index 66bcdcc..5ccc811 100644 --- a/src/MessageHandler/UpdateRdapServersHandler.php +++ b/src/MessageHandler/UpdateRdapServersHandler.php @@ -33,6 +33,11 @@ final readonly class UpdateRdapServersHandler /** @var \Throwable[] $throws */ $throws = []; + /* + * First, we update the list of TLDs from IANA because it is the official list of TLDs. + * Then, we update from ICANN because it allows to have information about generic top-level domains (gTLDs). + */ + try { $this->RDAPService->updateTldListIANA(); $this->RDAPService->updateGTldListICANN(); @@ -40,12 +45,20 @@ final readonly class UpdateRdapServersHandler $throws[] = $throwable; } + /* + * Finally, we take the list from IANA and import it again to allow the classification of the latest types of TLDs. + */ + try { $this->RDAPService->updateRDAPServersFromIANA(); } catch (\Throwable $throwable) { $throws[] = $throwable; } + /* + * If it exists, the list of custom RDAP servers is updated at this time. + */ + try { $this->RDAPService->updateRDAPServersFromFile($this->bag->get('custom_rdap_servers_file')); } catch (\Throwable $throwable) { diff --git a/src/Service/ChatNotificationService.php b/src/Service/ChatNotificationService.php index 410a6cf..aed091b 100644 --- a/src/Service/ChatNotificationService.php +++ b/src/Service/ChatNotificationService.php @@ -22,52 +22,54 @@ readonly class ChatNotificationService public function sendChatNotification(WatchList $watchList, DomainWatchdogNotification $notification): void { $webhookDsn = $watchList->getWebhookDsn(); - if (null !== $webhookDsn && 0 !== count($webhookDsn)) { - foreach ($webhookDsn as $dsnString) { - try { - $dsn = new Dsn($dsnString); - } catch (InvalidArgumentException $exception) { - throw new BadRequestHttpException($exception->getMessage()); + if (null === $webhookDsn || 0 === count($webhookDsn)) { + return; + } + + foreach ($webhookDsn as $dsnString) { + try { + $dsn = new Dsn($dsnString); + } catch (InvalidArgumentException $exception) { + throw new BadRequestHttpException($exception->getMessage()); + } + + $scheme = $dsn->getScheme(); + $webhookScheme = WebhookScheme::tryFrom($scheme); + + if (null === $webhookScheme) { + throw new BadRequestHttpException("The DSN scheme ($scheme) is not supported"); + } + + $transportFactoryClass = $webhookScheme->getChatTransportFactory(); + /** @var AbstractTransportFactory $transportFactory */ + $transportFactory = new $transportFactoryClass(); + + $push = $notification->asPushMessage(new NoRecipient()); + $chat = $notification->asChatMessage(new NoRecipient(), $webhookScheme->value); + + try { + $factory = $transportFactory->create($dsn); + + if ($factory->supports($push)) { + $factory->send($push); + } elseif ($factory->supports($chat)) { + $factory->send($chat); + } else { + throw new BadRequestHttpException('Unsupported message type'); } - $scheme = $dsn->getScheme(); - $webhookScheme = WebhookScheme::tryFrom($scheme); - - if (null === $webhookScheme) { - throw new BadRequestHttpException("The DSN scheme ($scheme) is not supported"); - } - - $transportFactoryClass = $webhookScheme->getChatTransportFactory(); - /** @var AbstractTransportFactory $transportFactory */ - $transportFactory = new $transportFactoryClass(); - - $push = $notification->asPushMessage(new NoRecipient()); - $chat = $notification->asChatMessage(new NoRecipient(), $webhookScheme->value); - - try { - $factory = $transportFactory->create($dsn); - - if ($factory->supports($push)) { - $factory->send($push); - } elseif ($factory->supports($chat)) { - $factory->send($chat); - } else { - throw new BadRequestHttpException('Unsupported message type'); - } - - $this->logger->info('Chat message sent with {schema} for Watchlist {token}', - [ - 'scheme' => $webhookScheme->name, - 'token' => $watchList->getToken(), - ]); - } catch (\Throwable $exception) { - $this->logger->error('Unable to send a chat message to {scheme} for Watchlist {token}', - [ - 'scheme' => $webhookScheme->name, - 'token' => $watchList->getToken(), - ]); - throw new BadRequestHttpException($exception->getMessage()); - } + $this->logger->info('Chat message sent with {schema} for Watchlist {token}', + [ + 'scheme' => $webhookScheme->name, + 'token' => $watchList->getToken(), + ]); + } catch (\Throwable $exception) { + $this->logger->error('Unable to send a chat message to {scheme} for Watchlist {token}', + [ + 'scheme' => $webhookScheme->name, + 'token' => $watchList->getToken(), + ]); + throw new BadRequestHttpException($exception->getMessage()); } } }