diff --git a/composer.json b/composer.json index e3b4491..77861df 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "runtime/frankenphp-symfony": "^0.2.0", "symfony/asset": "7.1.*", "symfony/asset-mapper": "7.1.*", + "symfony/cache": "7.1.*", "symfony/console": "7.1.*", "symfony/discord-notifier": "7.1.*", "symfony/doctrine-messenger": "7.1.*", diff --git a/composer.lock b/composer.lock index 657297b..2646bbc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f64fa606b60efd34dccdee3abcdad8b2", + "content-hash": "083beb16a31ddb88f798736f50894a76", "packages": [ { "name": "api-platform/core", @@ -3765,16 +3765,16 @@ }, { "name": "symfony/cache", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae" + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/e933e1d947ffb88efcdd34a2bd51561cab7deaae", - "reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae", + "url": "https://api.github.com/repos/symfony/cache/zipball/8ac37acee794372f9732fe8a61a8221f6762148e", + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e", "shasum": "" }, "require": { @@ -3842,7 +3842,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.1.2" + "source": "https://github.com/symfony/cache/tree/v7.1.3" }, "funding": [ { @@ -3858,7 +3858,7 @@ "type": "tidelift" } ], - "time": "2024-06-11T13:32:38+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/cache-contracts", diff --git a/src/Controller/StatisticsController.php b/src/Controller/StatisticsController.php new file mode 100644 index 0000000..a702f4c --- /dev/null +++ b/src/Controller/StatisticsController.php @@ -0,0 +1,74 @@ +setRdapQueries($this->pool->getItem('stats.rdap_queries.count')->get() ?? 0) + ->setDomainPurchased($this->pool->getItem('stats.domain.purchased')->get() ?? 0) + ->setDomainPurchaseFailed($this->pool->getItem('stats.domain.purchase.failed')->get() ?? 0) + ->setAlertSent($this->pool->getItem('stats.alert.sent')->get() ?? 0) + ->setWatchlistCount( + $this->getCachedItem('stats.watchlist.count', fn () => $this->watchListRepository->count() + )) + ->setDomainCount( + $this->getCachedItem('stats.domain.count', fn () => $this->domainRepository->createQueryBuilder('d') + ->join('d.tld', 't') + ->select('t.tld tld') + ->addSelect('COUNT(d.ldhName) AS domain') + ->addGroupBy('t.tld') + ->orderBy('domain', 'DESC') + ->setMaxResults(5) + ->getQuery()->getArrayResult()) + ) + ->setDomainCountTotal( + $this->getCachedItem('stats.domain.total', fn () => $this->domainRepository->count() + )); + + return $stats; + } + + /** + * @throws InvalidArgumentException + */ + private function getCachedItem(string $key, callable $getItemFunction) + { + $item = $this->pool->getItem($key); + + if (!$item->isHit() || $this->kernel->isDebug()) { + $value = $getItemFunction(); + $item + ->set($value) + ->expiresAfter(24 * 60 * 60); + $this->pool->save($item); + + return $value; + } else { + return $item->get(); + } + } +} diff --git a/src/Entity/Statistics.php b/src/Entity/Statistics.php new file mode 100644 index 0000000..e68bb3e --- /dev/null +++ b/src/Entity/Statistics.php @@ -0,0 +1,125 @@ +rdapQueries; + } + + public function setRdapQueries(?int $rdapQueries): static + { + $this->rdapQueries = $rdapQueries; + + return $this; + } + + public function getAlertSent(): ?int + { + return $this->alertSent; + } + + public function setAlertSent(?int $alertSent): static + { + $this->alertSent = $alertSent; + + return $this; + } + + public function getDomainPurchased(): ?int + { + return $this->domainPurchased; + } + + public function setDomainPurchased(?int $domainPurchased): static + { + $this->domainPurchased = $domainPurchased; + + return $this; + } + + public function getDomainCount(): ?array + { + return $this->domainCount; + } + + public function setDomainCount(?array $domainCount): static + { + $this->domainCount = $domainCount; + + return $this; + } + + public function getWatchlistCount(): ?int + { + return $this->watchlistCount; + } + + public function setWatchlistCount(?int $watchlistCount): static + { + $this->watchlistCount = $watchlistCount; + + return $this; + } + + public function getDomainCountTotal(): ?int + { + return $this->domainCountTotal; + } + + public function setDomainCountTotal(?int $domainCountTotal): void + { + $this->domainCountTotal = $domainCountTotal; + } + + public function getDomainPurchaseFailed(): ?int + { + return $this->domainPurchaseFailed; + } + + public function setDomainPurchaseFailed(?int $domainPurchaseFailed): static + { + $this->domainPurchaseFailed = $domainPurchaseFailed; + + return $this; + } + + public static function updateRDAPQueriesStat(CacheItemPoolInterface $pool, string $key): bool + { + try { + $item = $pool->getItem($key); + $item->set(($item->get() ?? 0) + 1); + + return $pool->save($item); + } catch (\Throwable) { + } + + return false; + } +} diff --git a/src/MessageHandler/ProcessDomainTriggerHandler.php b/src/MessageHandler/ProcessDomainTriggerHandler.php index 7c68d7f..8652a8a 100644 --- a/src/MessageHandler/ProcessDomainTriggerHandler.php +++ b/src/MessageHandler/ProcessDomainTriggerHandler.php @@ -7,6 +7,7 @@ use App\Config\TriggerAction; use App\Config\WebhookScheme; use App\Entity\Domain; use App\Entity\DomainEvent; +use App\Entity\Statistics; use App\Entity\WatchList; use App\Entity\WatchListTrigger; use App\Message\ProcessDomainTrigger; @@ -15,6 +16,7 @@ use App\Notifier\DomainOrderNotification; use App\Notifier\DomainUpdateNotification; use App\Repository\DomainRepository; use App\Repository\WatchListRepository; +use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; @@ -41,7 +43,7 @@ final readonly class ProcessDomainTriggerHandler private KernelInterface $kernel, private LoggerInterface $logger, private HttpClientInterface $client, - private MailerInterface $mailer + private MailerInterface $mailer, private CacheItemPoolInterface $cacheItemPool ) { $this->sender = new Address($mailerSenderEmail, $mailerSenderName); } @@ -82,6 +84,8 @@ final readonly class ProcessDomainTriggerHandler $notification = (new DomainOrderNotification($this->sender, $domain, $connector)); $this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage()); $this->sendChatNotification($watchList, $notification); + + Statistics::updateRDAPQueriesStat($this->cacheItemPool, 'stats.domain.purchased'); } catch (\Throwable) { $this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [ 'username' => $watchList->getUser()->getUserIdentifier(), @@ -90,6 +94,8 @@ final readonly class ProcessDomainTriggerHandler $notification = (new DomainOrderErrorNotification($this->sender, $domain)); $this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage()); $this->sendChatNotification($watchList, $notification); + + Statistics::updateRDAPQueriesStat($this->cacheItemPool, 'stats.domain.purchase.failed'); } } @@ -114,6 +120,8 @@ final readonly class ProcessDomainTriggerHandler } elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) { $this->sendChatNotification($watchList, $notification); } + + Statistics::updateRDAPQueriesStat($this->cacheItemPool, 'stats.alert.sent'); } } } diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index 0296812..89c5391 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -12,6 +12,7 @@ use App\Entity\EntityEvent; use App\Entity\Nameserver; use App\Entity\NameserverEntity; use App\Entity\RdapServer; +use App\Entity\Statistics; use App\Entity\Tld; use App\Repository\DomainEntityRepository; use App\Repository\DomainEventRepository; @@ -24,6 +25,7 @@ use App\Repository\RdapServerRepository; use App\Repository\TldRepository; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\ORMException; +use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpFoundation\Exception\BadRequestException; @@ -87,7 +89,8 @@ readonly class RDAPService private RdapServerRepository $rdapServerRepository, private TldRepository $tldRepository, private EntityManagerInterface $em, - private LoggerInterface $logger + private LoggerInterface $logger, + private CacheItemPoolInterface $pool ) { } @@ -162,6 +165,8 @@ readonly class RDAPService ]); try { + Statistics::updateRDAPQueriesStat($this->pool, 'stats.rdap_queries.count'); + $res = $this->client->request( 'GET', $rdapServerUrl.'domain/'.$idnDomain )->toArray();