mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: add stats endpoint
This commit is contained in:
@@ -40,6 +40,7 @@
|
|||||||
"runtime/frankenphp-symfony": "^0.2.0",
|
"runtime/frankenphp-symfony": "^0.2.0",
|
||||||
"symfony/asset": "7.1.*",
|
"symfony/asset": "7.1.*",
|
||||||
"symfony/asset-mapper": "7.1.*",
|
"symfony/asset-mapper": "7.1.*",
|
||||||
|
"symfony/cache": "7.1.*",
|
||||||
"symfony/console": "7.1.*",
|
"symfony/console": "7.1.*",
|
||||||
"symfony/discord-notifier": "7.1.*",
|
"symfony/discord-notifier": "7.1.*",
|
||||||
"symfony/doctrine-messenger": "7.1.*",
|
"symfony/doctrine-messenger": "7.1.*",
|
||||||
|
|||||||
14
composer.lock
generated
14
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f64fa606b60efd34dccdee3abcdad8b2",
|
"content-hash": "083beb16a31ddb88f798736f50894a76",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/core",
|
"name": "api-platform/core",
|
||||||
@@ -3765,16 +3765,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/cache",
|
"name": "symfony/cache",
|
||||||
"version": "v7.1.2",
|
"version": "v7.1.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/cache.git",
|
"url": "https://github.com/symfony/cache.git",
|
||||||
"reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae"
|
"reference": "8ac37acee794372f9732fe8a61a8221f6762148e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/cache/zipball/e933e1d947ffb88efcdd34a2bd51561cab7deaae",
|
"url": "https://api.github.com/repos/symfony/cache/zipball/8ac37acee794372f9732fe8a61a8221f6762148e",
|
||||||
"reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae",
|
"reference": "8ac37acee794372f9732fe8a61a8221f6762148e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -3842,7 +3842,7 @@
|
|||||||
"psr6"
|
"psr6"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/cache/tree/v7.1.2"
|
"source": "https://github.com/symfony/cache/tree/v7.1.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3858,7 +3858,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-06-11T13:32:38+00:00"
|
"time": "2024-07-17T06:10:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/cache-contracts",
|
"name": "symfony/cache-contracts",
|
||||||
|
|||||||
74
src/Controller/StatisticsController.php
Normal file
74
src/Controller/StatisticsController.php
Normal 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
125
src/Entity/Statistics.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use App\Config\TriggerAction;
|
|||||||
use App\Config\WebhookScheme;
|
use App\Config\WebhookScheme;
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use App\Entity\DomainEvent;
|
use App\Entity\DomainEvent;
|
||||||
|
use App\Entity\Statistics;
|
||||||
use App\Entity\WatchList;
|
use App\Entity\WatchList;
|
||||||
use App\Entity\WatchListTrigger;
|
use App\Entity\WatchListTrigger;
|
||||||
use App\Message\ProcessDomainTrigger;
|
use App\Message\ProcessDomainTrigger;
|
||||||
@@ -15,6 +16,7 @@ use App\Notifier\DomainOrderNotification;
|
|||||||
use App\Notifier\DomainUpdateNotification;
|
use App\Notifier\DomainUpdateNotification;
|
||||||
use App\Repository\DomainRepository;
|
use App\Repository\DomainRepository;
|
||||||
use App\Repository\WatchListRepository;
|
use App\Repository\WatchListRepository;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||||
@@ -41,7 +43,7 @@ final readonly class ProcessDomainTriggerHandler
|
|||||||
private KernelInterface $kernel,
|
private KernelInterface $kernel,
|
||||||
private LoggerInterface $logger,
|
private LoggerInterface $logger,
|
||||||
private HttpClientInterface $client,
|
private HttpClientInterface $client,
|
||||||
private MailerInterface $mailer
|
private MailerInterface $mailer, private CacheItemPoolInterface $cacheItemPool
|
||||||
) {
|
) {
|
||||||
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
|
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
|
||||||
}
|
}
|
||||||
@@ -82,6 +84,8 @@ final readonly class ProcessDomainTriggerHandler
|
|||||||
$notification = (new DomainOrderNotification($this->sender, $domain, $connector));
|
$notification = (new DomainOrderNotification($this->sender, $domain, $connector));
|
||||||
$this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage());
|
$this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage());
|
||||||
$this->sendChatNotification($watchList, $notification);
|
$this->sendChatNotification($watchList, $notification);
|
||||||
|
|
||||||
|
Statistics::updateRDAPQueriesStat($this->cacheItemPool, 'stats.domain.purchased');
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable) {
|
||||||
$this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [
|
$this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [
|
||||||
'username' => $watchList->getUser()->getUserIdentifier(),
|
'username' => $watchList->getUser()->getUserIdentifier(),
|
||||||
@@ -90,6 +94,8 @@ final readonly class ProcessDomainTriggerHandler
|
|||||||
$notification = (new DomainOrderErrorNotification($this->sender, $domain));
|
$notification = (new DomainOrderErrorNotification($this->sender, $domain));
|
||||||
$this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage());
|
$this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage());
|
||||||
$this->sendChatNotification($watchList, $notification);
|
$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()) {
|
} elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) {
|
||||||
$this->sendChatNotification($watchList, $notification);
|
$this->sendChatNotification($watchList, $notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Statistics::updateRDAPQueriesStat($this->cacheItemPool, 'stats.alert.sent');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use App\Entity\EntityEvent;
|
|||||||
use App\Entity\Nameserver;
|
use App\Entity\Nameserver;
|
||||||
use App\Entity\NameserverEntity;
|
use App\Entity\NameserverEntity;
|
||||||
use App\Entity\RdapServer;
|
use App\Entity\RdapServer;
|
||||||
|
use App\Entity\Statistics;
|
||||||
use App\Entity\Tld;
|
use App\Entity\Tld;
|
||||||
use App\Repository\DomainEntityRepository;
|
use App\Repository\DomainEntityRepository;
|
||||||
use App\Repository\DomainEventRepository;
|
use App\Repository\DomainEventRepository;
|
||||||
@@ -24,6 +25,7 @@ use App\Repository\RdapServerRepository;
|
|||||||
use App\Repository\TldRepository;
|
use App\Repository\TldRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Exception\ORMException;
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||||
@@ -87,7 +89,8 @@ readonly class RDAPService
|
|||||||
private RdapServerRepository $rdapServerRepository,
|
private RdapServerRepository $rdapServerRepository,
|
||||||
private TldRepository $tldRepository,
|
private TldRepository $tldRepository,
|
||||||
private EntityManagerInterface $em,
|
private EntityManagerInterface $em,
|
||||||
private LoggerInterface $logger
|
private LoggerInterface $logger,
|
||||||
|
private CacheItemPoolInterface $pool
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +165,8 @@ readonly class RDAPService
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
Statistics::updateRDAPQueriesStat($this->pool, 'stats.rdap_queries.count');
|
||||||
|
|
||||||
$res = $this->client->request(
|
$res = $this->client->request(
|
||||||
'GET', $rdapServerUrl.'domain/'.$idnDomain
|
'GET', $rdapServerUrl.'domain/'.$idnDomain
|
||||||
)->toArray();
|
)->toArray();
|
||||||
|
|||||||
Reference in New Issue
Block a user