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",
|
||||
"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
14
composer.lock
generated
@@ -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",
|
||||
|
||||
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\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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user