feat: add stats endpoint

This commit is contained in:
Maël Gangloff
2024-08-22 18:11:07 +02:00
parent 274fffb204
commit 6601540eb4
6 changed files with 222 additions and 9 deletions

View File

@@ -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.*",

14
composer.lock generated
View File

@@ -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",

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Controller;
use App\Entity\Statistics;
use App\Repository\DomainRepository;
use App\Repository\WatchListRepository;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\KernelInterface;
class StatisticsController extends AbstractController
{
public function __construct(
private readonly CacheItemPoolInterface $pool,
private readonly DomainRepository $domainRepository,
private readonly WatchListRepository $watchListRepository,
private readonly KernelInterface $kernel
) {
}
/**
* @throws InvalidArgumentException
*/
public function __invoke(): Statistics
{
$stats = new Statistics();
$stats
->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();
}
}
}

125
src/Entity/Statistics.php Normal file
View File

@@ -0,0 +1,125 @@
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use App\Controller\StatisticsController;
use Psr\Cache\CacheItemPoolInterface;
#[ApiResource(
operations: [
new Get(
uriTemplate: '/stats',
controller: StatisticsController::class,
shortName: 'Statistics',
read: false,
),
]
)]
class Statistics
{
private ?int $rdapQueries = null;
private ?int $alertSent = null;
private ?int $domainPurchased = null;
private ?int $domainPurchaseFailed = null;
private ?array $domainCount = null;
private ?int $domainCountTotal = null;
private ?int $watchlistCount = null;
public function getRdapQueries(): ?int
{
return $this->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;
}
}

View File

@@ -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');
}
}
}

View File

@@ -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();