diff --git a/src/Command/RegisterDomainCommand.php b/src/Command/RegisterDomainCommand.php new file mode 100644 index 0000000..8d6647b --- /dev/null +++ b/src/Command/RegisterDomainCommand.php @@ -0,0 +1,61 @@ +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; + } +} diff --git a/src/Controller/DomainRefreshController.php b/src/Controller/DomainRefreshController.php index 63de9d0..0bb6adc 100644 --- a/src/Controller/DomainRefreshController.php +++ b/src/Controller/DomainRefreshController.php @@ -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 (null !== $domain && !$domain->getDeleted() - && ($domain->getUpdatedAt()->diff(new \DateTimeImmutable('now'))->days < 7) - && !$this->RDAPService::isToBeWatchClosely($domain) + && !$domain->isToBeUpdated() && !$this->kernel->isDebug() ) { $this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [ diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index 30d2b01..83a88eb 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -4,6 +4,7 @@ namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use App\Config\EventAction; use App\Controller\DomainRefreshController; use App\Repository\DomainRepository; use Doctrine\Common\Collections\ArrayCollection; @@ -105,6 +106,20 @@ class Domain #[Groups(['domain:item', 'domain:list'])] 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() { $this->events = new ArrayCollection(); @@ -317,4 +332,49 @@ class Domain 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); + } } diff --git a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php index fe7ba4f..fef0726 100644 --- a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php +++ b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php @@ -68,19 +68,7 @@ final readonly class UpdateDomainsFromWatchlistHandler */ /** @var Domain $domain */ - foreach ($watchList->getDomains() - ->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 + foreach ($watchList->getDomains()->filter(fn ($domain) => $domain->isToBeUpdated(false)) as $domain ) { $updatedAt = $domain->getUpdatedAt(); diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index c94848b..e8506e2 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -74,18 +74,6 @@ readonly class RDAPService '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, private EntityRepository $entityRepository, 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 TransportExceptionInterface * @throws DecodingExceptionInterface - * @throws \Throwable */ public function registerDomains(array $domains): void { @@ -144,7 +108,7 @@ readonly class RDAPService * @throws RedirectionExceptionInterface * @throws DecodingExceptionInterface * @throws ClientExceptionInterface - * @throws \Throwable + * @throws \Exception */ public function registerDomain(string $fqdn): Domain { @@ -179,7 +143,7 @@ readonly class RDAPService $res = $this->client->request( 'GET', $rdapServerUrl.'domain/'.$idnDomain )->toArray(); - } catch (\Throwable $e) { + } catch (\Exception $e) { if ($e instanceof ClientException && 404 === $e->getResponse()->getStatusCode()) { if (null !== $domain) { $this->logger->notice('The domain name {idnDomain} has been deleted from the WHOIS database.', [