mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
docs: add documentation on MessageHandler
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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}.', [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user