mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: add app:register-domain command
This commit is contained in:
61
src/Command/RegisterDomainCommand.php
Normal file
61
src/Command/RegisterDomainCommand.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Repository\DomainRepository;
|
||||||
|
use App\Service\RDAPService;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:register-domain',
|
||||||
|
description: 'Register a domain name in the database',
|
||||||
|
)]
|
||||||
|
class RegisterDomainCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DomainRepository $domainRepository,
|
||||||
|
private readonly RDAPService $rdapService,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addArgument('domain', InputArgument::REQUIRED, 'The domain name to register')
|
||||||
|
->addOption('force', 'f', InputOption::VALUE_NEGATABLE, 'Do not check the freshness of the data and still make the query', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$ldhName = strtolower(idn_to_ascii($input->getArgument('domain')));
|
||||||
|
$force = (bool) $input->getOption('force');
|
||||||
|
$domain = $this->domainRepository->findOneBy(['ldhName' => $ldhName]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (null !== $domain && !$force) {
|
||||||
|
if (!$domain->isToBeUpdated()) {
|
||||||
|
$io->warning('The domain name is already present in the database and does not need to be updated at this time.');
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->rdapService->registerDomain($ldhName);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$io->error($e->getMessage());
|
||||||
|
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->success('The domain name has been successfully registered in the database.');
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,8 +53,7 @@ class DomainRefreshController extends AbstractController
|
|||||||
// If the domain name exists in the database, recently updated and not important, we return the stored Domain
|
// If the domain name exists in the database, recently updated and not important, we return the stored Domain
|
||||||
if (null !== $domain
|
if (null !== $domain
|
||||||
&& !$domain->getDeleted()
|
&& !$domain->getDeleted()
|
||||||
&& ($domain->getUpdatedAt()->diff(new \DateTimeImmutable('now'))->days < 7)
|
&& !$domain->isToBeUpdated()
|
||||||
&& !$this->RDAPService::isToBeWatchClosely($domain)
|
|
||||||
&& !$this->kernel->isDebug()
|
&& !$this->kernel->isDebug()
|
||||||
) {
|
) {
|
||||||
$this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [
|
$this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use App\Config\EventAction;
|
||||||
use App\Controller\DomainRefreshController;
|
use App\Controller\DomainRefreshController;
|
||||||
use App\Repository\DomainRepository;
|
use App\Repository\DomainRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -105,6 +106,20 @@ class Domain
|
|||||||
#[Groups(['domain:item', 'domain:list'])]
|
#[Groups(['domain:item', 'domain:list'])]
|
||||||
private ?bool $deleted;
|
private ?bool $deleted;
|
||||||
|
|
||||||
|
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];
|
||||||
|
private const IMPORTANT_STATUS = [
|
||||||
|
'redemption period',
|
||||||
|
'pending delete',
|
||||||
|
'pending create',
|
||||||
|
'pending renew',
|
||||||
|
'pending restore',
|
||||||
|
'pending transfer',
|
||||||
|
'pending update',
|
||||||
|
'add period',
|
||||||
|
'client hold',
|
||||||
|
'server hold',
|
||||||
|
];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->events = new ArrayCollection();
|
$this->events = new ArrayCollection();
|
||||||
@@ -317,4 +332,49 @@ class Domain
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a domain name needs special attention.
|
||||||
|
* These domain names are those whose last event was expiration or deletion.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function isToBeWatchClosely(): bool
|
||||||
|
{
|
||||||
|
$status = $this->getStatus();
|
||||||
|
if ((!empty($status) && count(array_intersect($status, self::IMPORTANT_STATUS))) || $this->getDeleted()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var DomainEvent[] $events */
|
||||||
|
$events = $this->getEvents()
|
||||||
|
->filter(fn (DomainEvent $e) => $e->getDate() <= new \DateTimeImmutable('now'))
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
usort($events, fn (DomainEvent $e1, DomainEvent $e2) => $e2->getDate() <=> $e1->getDate());
|
||||||
|
|
||||||
|
return !empty($events) && in_array($events[0]->getAction(), self::IMPORTANT_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if one or more of these conditions are met:
|
||||||
|
* - It has been more than 7 days since the domain name was last updated
|
||||||
|
* - It has been more than 1 hour and the domain name has statuses that suggest it is not stable
|
||||||
|
* - It has been more than 1 day and the domain name is blocked in DNS
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function isToBeUpdated(bool $fromUser = true): bool
|
||||||
|
{
|
||||||
|
return $this->getUpdatedAt()
|
||||||
|
->diff(new \DateTimeImmutable())->days >= 7
|
||||||
|
|| (
|
||||||
|
($fromUser || ($this->getUpdatedAt()
|
||||||
|
->diff(new \DateTimeImmutable())->h * 60 + $this->getUpdatedAt()
|
||||||
|
->diff(new \DateTimeImmutable())->i) >= 50)
|
||||||
|
&& $this->isToBeWatchClosely()
|
||||||
|
)
|
||||||
|
|| (count(array_intersect($this->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
|
||||||
|
&& $this->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,19 +68,7 @@ final readonly class UpdateDomainsFromWatchlistHandler
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** @var Domain $domain */
|
/** @var Domain $domain */
|
||||||
foreach ($watchList->getDomains()
|
foreach ($watchList->getDomains()->filter(fn ($domain) => $domain->isToBeUpdated(false)) as $domain
|
||||||
->filter(fn ($domain) => $domain->getUpdatedAt()
|
|
||||||
->diff(new \DateTimeImmutable())->days >= 7
|
|
||||||
|| (
|
|
||||||
($domain->getUpdatedAt()
|
|
||||||
->diff(new \DateTimeImmutable())->h * 60 + $domain->getUpdatedAt()
|
|
||||||
->diff(new \DateTimeImmutable())->i) >= 50
|
|
||||||
&& $this->RDAPService::isToBeWatchClosely($domain)
|
|
||||||
)
|
|
||||||
|| (count(array_intersect($domain->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
|
|
||||||
&& $domain->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1
|
|
||||||
)
|
|
||||||
) as $domain
|
|
||||||
) {
|
) {
|
||||||
$updatedAt = $domain->getUpdatedAt();
|
$updatedAt = $domain->getUpdatedAt();
|
||||||
|
|
||||||
|
|||||||
@@ -74,18 +74,6 @@ readonly class RDAPService
|
|||||||
'xn--hlcj6aya9esc7a',
|
'xn--hlcj6aya9esc7a',
|
||||||
];
|
];
|
||||||
|
|
||||||
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];
|
|
||||||
private const IMPORTANT_STATUS = [
|
|
||||||
'redemption period',
|
|
||||||
'pending delete',
|
|
||||||
'pending create',
|
|
||||||
'pending renew',
|
|
||||||
'pending restore',
|
|
||||||
'pending transfer',
|
|
||||||
'pending update',
|
|
||||||
'add period',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function __construct(private HttpClientInterface $client,
|
public function __construct(private HttpClientInterface $client,
|
||||||
private EntityRepository $entityRepository,
|
private EntityRepository $entityRepository,
|
||||||
private DomainRepository $domainRepository,
|
private DomainRepository $domainRepository,
|
||||||
@@ -102,34 +90,10 @@ readonly class RDAPService
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if a domain name needs special attention.
|
|
||||||
* These domain names are those whose last event was expiration or deletion.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public static function isToBeWatchClosely(Domain $domain): bool
|
|
||||||
{
|
|
||||||
$status = $domain->getStatus();
|
|
||||||
if ((!empty($status) && count(array_intersect($status, self::IMPORTANT_STATUS))) || $domain->getDeleted()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var DomainEvent[] $events */
|
|
||||||
$events = $domain->getEvents()
|
|
||||||
->filter(fn (DomainEvent $e) => $e->getDate() <= new \DateTimeImmutable('now'))
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
usort($events, fn (DomainEvent $e1, DomainEvent $e2) => $e2->getDate() <=> $e1->getDate());
|
|
||||||
|
|
||||||
return !empty($events) && in_array($events[0]->getAction(), self::IMPORTANT_EVENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws HttpExceptionInterface
|
* @throws HttpExceptionInterface
|
||||||
* @throws TransportExceptionInterface
|
* @throws TransportExceptionInterface
|
||||||
* @throws DecodingExceptionInterface
|
* @throws DecodingExceptionInterface
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
*/
|
||||||
public function registerDomains(array $domains): void
|
public function registerDomains(array $domains): void
|
||||||
{
|
{
|
||||||
@@ -144,7 +108,7 @@ readonly class RDAPService
|
|||||||
* @throws RedirectionExceptionInterface
|
* @throws RedirectionExceptionInterface
|
||||||
* @throws DecodingExceptionInterface
|
* @throws DecodingExceptionInterface
|
||||||
* @throws ClientExceptionInterface
|
* @throws ClientExceptionInterface
|
||||||
* @throws \Throwable
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function registerDomain(string $fqdn): Domain
|
public function registerDomain(string $fqdn): Domain
|
||||||
{
|
{
|
||||||
@@ -179,7 +143,7 @@ readonly class RDAPService
|
|||||||
$res = $this->client->request(
|
$res = $this->client->request(
|
||||||
'GET', $rdapServerUrl.'domain/'.$idnDomain
|
'GET', $rdapServerUrl.'domain/'.$idnDomain
|
||||||
)->toArray();
|
)->toArray();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Exception $e) {
|
||||||
if ($e instanceof ClientException && 404 === $e->getResponse()->getStatusCode()) {
|
if ($e instanceof ClientException && 404 === $e->getResponse()->getStatusCode()) {
|
||||||
if (null !== $domain) {
|
if (null !== $domain) {
|
||||||
$this->logger->notice('The domain name {idnDomain} has been deleted from the WHOIS database.', [
|
$this->logger->notice('The domain name {idnDomain} has been deleted from the WHOIS database.', [
|
||||||
|
|||||||
Reference in New Issue
Block a user