mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
refactor: move logic in RDAPService
This commit is contained in:
@@ -48,7 +48,7 @@ class RegisterDomainCommand extends Command
|
||||
|
||||
try {
|
||||
if (null !== $domain && !$force) {
|
||||
if (!$domain->isToBeUpdated(true, true)) {
|
||||
if (!$this->rdapService->isToBeUpdated($domain, true, true)) {
|
||||
$io->warning('The domain name is already present in the database and does not need to be updated at this time.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Entity\User;
|
||||
use App\Entity\WatchList;
|
||||
use App\Repository\DomainEventRepository;
|
||||
use App\Repository\WatchListRepository;
|
||||
use App\Service\CalendarService;
|
||||
use App\Service\RDAPService;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Eluceo\iCal\Domain\Entity\Calendar;
|
||||
@@ -30,7 +31,7 @@ class WatchListController extends AbstractController
|
||||
public function __construct(
|
||||
private readonly WatchListRepository $watchListRepository,
|
||||
private readonly DomainEventRepository $domainEventRepository,
|
||||
private readonly RDAPService $RDAPService,
|
||||
private readonly RDAPService $RDAPService, private readonly CalendarService $calendarService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ class WatchListController extends AbstractController
|
||||
|
||||
/** @var Domain $domain */
|
||||
foreach ($watchList->getDomains()->getIterator() as $domain) {
|
||||
foreach ($domain->getDomainCalendarEvents() as $event) {
|
||||
foreach ($this->calendarService->getDomainCalendarEvents($domain) as $event) {
|
||||
$calendar->addEvent($event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,6 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Eluceo\iCal\Domain\Entity\Attendee;
|
||||
use Eluceo\iCal\Domain\Entity\Event;
|
||||
use Eluceo\iCal\Domain\Enum\EventStatus;
|
||||
use Eluceo\iCal\Domain\ValueObject\Category;
|
||||
use Eluceo\iCal\Domain\ValueObject\Date;
|
||||
use Eluceo\iCal\Domain\ValueObject\EmailAddress;
|
||||
use Eluceo\iCal\Domain\ValueObject\SingleDay;
|
||||
use Eluceo\iCal\Domain\ValueObject\Timestamp;
|
||||
use Sabre\VObject\EofException;
|
||||
use Sabre\VObject\InvalidDataException;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Reader;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
|
||||
@@ -387,7 +375,7 @@ class Domain
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function isToBeWatchClosely(): bool
|
||||
public function isToBeWatchClosely(): bool
|
||||
{
|
||||
$status = $this->getStatus();
|
||||
if ((!empty($status) && count(array_intersect($status, self::IMPORTANT_STATUS))) || $this->getDeleted()) {
|
||||
@@ -404,47 +392,6 @@ class Domain
|
||||
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 12 minutes 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 $intensifyLastDay = false): bool
|
||||
{
|
||||
$updatedAtDiff = $this->getUpdatedAt()->diff(new \DateTimeImmutable());
|
||||
|
||||
if ($updatedAtDiff->days >= 7) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->getDeleted()) {
|
||||
return $fromUser;
|
||||
}
|
||||
|
||||
$expiresIn = $this->getExpiresInDays();
|
||||
|
||||
if ($intensifyLastDay && (0 === $expiresIn || 1 === $expiresIn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$minutesDiff = $updatedAtDiff->h * 60 + $updatedAtDiff->i;
|
||||
if (($minutesDiff >= 12 || $fromUser) && $this->isToBeWatchClosely()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
count(array_intersect($this->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
|
||||
&& $updatedAtDiff->days >= 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, DomainStatus>
|
||||
*/
|
||||
@@ -539,72 +486,6 @@ class Domain
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Event[]
|
||||
*
|
||||
* @throws ParseException
|
||||
* @throws EofException
|
||||
* @throws InvalidDataException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDomainCalendarEvents(): array
|
||||
{
|
||||
$events = [];
|
||||
$attendees = [];
|
||||
|
||||
/* @var DomainEntity $entity */
|
||||
foreach ($this->getDomainEntities()->filter(fn (DomainEntity $domainEntity) => !$domainEntity->getDeletedAt())->getIterator() as $domainEntity) {
|
||||
$jCard = $domainEntity->getEntity()->getJCard();
|
||||
|
||||
if (empty($jCard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vCardData = Reader::readJson($jCard);
|
||||
|
||||
if (empty($vCardData->EMAIL) || empty($vCardData->FN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = (string) $vCardData->EMAIL;
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attendees[] = (new Attendee(new EmailAddress($email)))->setDisplayName((string) $vCardData->FN);
|
||||
}
|
||||
|
||||
/** @var DomainEvent $event */
|
||||
foreach ($this->getEvents()->filter(fn (DomainEvent $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $event) {
|
||||
$events[] = (new Event())
|
||||
->setLastModified(new Timestamp($this->getUpdatedAt()))
|
||||
->setStatus(EventStatus::CONFIRMED())
|
||||
->setSummary($this->getLdhName().': '.$event->getAction())
|
||||
->addCategory(new Category($event->getAction()))
|
||||
->setAttendees($attendees)
|
||||
->setOccurrence(new SingleDay(new Date($event->getDate()))
|
||||
);
|
||||
}
|
||||
|
||||
$expiresInDays = $this->getExpiresInDays();
|
||||
|
||||
if (null !== $expiresInDays) {
|
||||
$events[] = (new Event())
|
||||
->setLastModified(new Timestamp($this->getUpdatedAt()))
|
||||
->setStatus(EventStatus::CONFIRMED())
|
||||
->setSummary($this->getLdhName().': estimated WHOIS release date')
|
||||
->addCategory(new Category('release'))
|
||||
->setAttendees($attendees)
|
||||
->setOccurrence(new SingleDay(new Date(
|
||||
(new \DateTimeImmutable())->setTime(0, 0)->add(new \DateInterval('P'.$expiresInDays.'D'))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
#[Groups(['domain:item', 'domain:list'])]
|
||||
public function getExpiresInDays(): ?int
|
||||
{
|
||||
|
||||
@@ -96,7 +96,7 @@ final readonly class UpdateDomainsFromWatchlistHandler
|
||||
*/
|
||||
|
||||
/** @var Domain $domain */
|
||||
foreach ($watchList->getDomains()->filter(fn ($domain) => $domain->isToBeUpdated(false, null !== $watchList->getConnector())) as $domain
|
||||
foreach ($watchList->getDomains()->filter(fn ($domain) => $this->RDAPService->isToBeUpdated($domain, false, null !== $watchList->getConnector())) as $domain
|
||||
) {
|
||||
$updatedAt = $domain->getUpdatedAt();
|
||||
$deleted = $domain->getDeleted();
|
||||
|
||||
93
src/Service/CalendarService.php
Normal file
93
src/Service/CalendarService.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Domain;
|
||||
use App\Entity\DomainEntity;
|
||||
use App\Entity\DomainEvent;
|
||||
use Eluceo\iCal\Domain\Entity\Attendee;
|
||||
use Eluceo\iCal\Domain\Entity\Event;
|
||||
use Eluceo\iCal\Domain\Enum\EventStatus;
|
||||
use Eluceo\iCal\Domain\ValueObject\Category;
|
||||
use Eluceo\iCal\Domain\ValueObject\Date;
|
||||
use Eluceo\iCal\Domain\ValueObject\EmailAddress;
|
||||
use Eluceo\iCal\Domain\ValueObject\SingleDay;
|
||||
use Eluceo\iCal\Domain\ValueObject\Timestamp;
|
||||
use Sabre\VObject\EofException;
|
||||
use Sabre\VObject\InvalidDataException;
|
||||
use Sabre\VObject\ParseException;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
readonly class CalendarService
|
||||
{
|
||||
public function __construct(
|
||||
private RDAPService $RDAPService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Event[]
|
||||
*
|
||||
* @throws ParseException
|
||||
* @throws EofException
|
||||
* @throws InvalidDataException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDomainCalendarEvents(Domain $domain): array
|
||||
{
|
||||
$events = [];
|
||||
$attendees = [];
|
||||
|
||||
/* @var DomainEntity $entity */
|
||||
foreach ($domain->getDomainEntities()->filter(fn (DomainEntity $domainEntity) => !$domainEntity->getDeletedAt())->getIterator() as $domainEntity) {
|
||||
$jCard = $domainEntity->getEntity()->getJCard();
|
||||
|
||||
if (empty($jCard)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vCardData = Reader::readJson($jCard);
|
||||
|
||||
if (empty($vCardData->EMAIL) || empty($vCardData->FN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = (string) $vCardData->EMAIL;
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attendees[] = (new Attendee(new EmailAddress($email)))->setDisplayName((string) $vCardData->FN);
|
||||
}
|
||||
|
||||
/** @var DomainEvent $event */
|
||||
foreach ($domain->getEvents()->filter(fn (DomainEvent $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $event) {
|
||||
$events[] = (new Event())
|
||||
->setLastModified(new Timestamp($domain->getUpdatedAt()))
|
||||
->setStatus(EventStatus::CONFIRMED())
|
||||
->setSummary($domain->getLdhName().': '.$event->getAction())
|
||||
->addCategory(new Category($event->getAction()))
|
||||
->setAttendees($attendees)
|
||||
->setOccurrence(new SingleDay(new Date($event->getDate()))
|
||||
);
|
||||
}
|
||||
|
||||
$expiresInDays = $this->RDAPService->getExpiresInDays($domain);
|
||||
|
||||
if (null !== $expiresInDays) {
|
||||
$events[] = (new Event())
|
||||
->setLastModified(new Timestamp($domain->getUpdatedAt()))
|
||||
->setStatus(EventStatus::CONFIRMED())
|
||||
->setSummary($domain->getLdhName().': estimated WHOIS release date')
|
||||
->addCategory(new Category('release'))
|
||||
->setAttendees($attendees)
|
||||
->setOccurrence(new SingleDay(new Date(
|
||||
(new \DateTimeImmutable())->setTime(0, 0)->add(new \DateInterval('P'.$expiresInDays.'D'))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
||||
@@ -788,6 +788,47 @@ class RDAPService
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 12 minutes 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(Domain $domain, bool $fromUser = true, bool $intensifyLastDay = false): bool
|
||||
{
|
||||
$updatedAtDiff = $domain->getUpdatedAt()->diff(new \DateTimeImmutable());
|
||||
|
||||
if ($updatedAtDiff->days >= 7) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($domain->getDeleted()) {
|
||||
return $fromUser;
|
||||
}
|
||||
|
||||
$expiresIn = $this->getExpiresInDays($domain);
|
||||
|
||||
if ($intensifyLastDay && (0 === $expiresIn || 1 === $expiresIn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$minutesDiff = $updatedAtDiff->h * 60 + $updatedAtDiff->i;
|
||||
if (($minutesDiff >= 12 || $fromUser) && $domain->isToBeWatchClosely()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
count(array_intersect($domain->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0
|
||||
&& $updatedAtDiff->days >= 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
private function calculateDaysFromEvents(\DateTimeImmutable $now): ?int
|
||||
{
|
||||
|
||||
@@ -6,9 +6,14 @@ use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Entity\Domain;
|
||||
use App\Entity\WatchList;
|
||||
use App\Exception\DomainNotFoundException;
|
||||
use App\Exception\MalformedDomainException;
|
||||
use App\Exception\TldNotSupportedException;
|
||||
use App\Exception\UnknownRdapServerException;
|
||||
use App\Message\SendDomainEventNotif;
|
||||
use App\Repository\DomainRepository;
|
||||
use App\Service\RDAPService;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Random\Randomizer;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -16,8 +21,14 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Messenger\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
readonly class AutoRegisterDomainProvider implements ProviderInterface
|
||||
{
|
||||
@@ -34,6 +45,20 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws DomainNotFoundException
|
||||
* @throws TldNotSupportedException
|
||||
* @throws DecodingExceptionInterface
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws MalformedDomainException
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws UnknownRdapServerException
|
||||
* @throws ExceptionInterface
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
$userId = $this->security->getUser()->getUserIdentifier();
|
||||
@@ -51,7 +76,7 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface
|
||||
// If the domain name exists in the database, recently updated and not important, we return the stored Domain
|
||||
if (null !== $domain
|
||||
&& !$domain->getDeleted()
|
||||
&& !$domain->isToBeUpdated(true, true)
|
||||
&& !$this->RDAPService->isToBeUpdated($domain, true, true)
|
||||
&& ($request && !filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN))
|
||||
) {
|
||||
$this->logger->debug('It is not necessary to update the domain name', [
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Entity\Domain;
|
||||
use App\Entity\DomainEvent;
|
||||
use App\Entity\DomainStatus;
|
||||
use App\Exception\MalformedDomainException;
|
||||
use App\Service\RDAPService;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -49,9 +50,6 @@ final class DomainTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetExpiresInDays(): void
|
||||
{
|
||||
$this->assertNull(
|
||||
@@ -235,17 +233,21 @@ final class DomainTest extends TestCase
|
||||
array $status,
|
||||
bool $expected,
|
||||
): void {
|
||||
$mock = $this->getMockBuilder(Domain::class)
|
||||
->onlyMethods(['getUpdatedAt', 'getDeleted', 'getExpiresInDays', 'isToBeWatchClosely', 'getStatus'])
|
||||
$rdapServiceMock = $this->getMockBuilder(RDAPService::class)
|
||||
->onlyMethods(['getExpiresInDays'])
|
||||
->getMock();
|
||||
|
||||
$mock->method('getUpdatedAt')->willReturn($updatedAt);
|
||||
$mock->method('getDeleted')->willReturn($deleted);
|
||||
$mock->method('getExpiresInDays')->willReturn($expiresIn);
|
||||
$mock->method('isToBeWatchClosely')->willReturn($watchClosely);
|
||||
$mock->method('getStatus')->willReturn($status);
|
||||
$domainMock = $this->getMockBuilder(Domain::class)
|
||||
->onlyMethods(['getUpdatedAt', 'getDeleted', 'isToBeWatchClosely', 'getStatus'])
|
||||
->getMock();
|
||||
|
||||
$result = $mock->isToBeUpdated($fromUser, $intensifyLastDay);
|
||||
$domainMock->method('getUpdatedAt')->willReturn($updatedAt);
|
||||
$domainMock->method('getDeleted')->willReturn($deleted);
|
||||
$rdapServiceMock->method('getExpiresInDays')->willReturn($expiresIn);
|
||||
$domainMock->method('isToBeWatchClosely')->willReturn($watchClosely);
|
||||
$domainMock->method('getStatus')->willReturn($status);
|
||||
|
||||
$result = $rdapServiceMock->isToBeUpdated($domainMock, $fromUser, $intensifyLastDay);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ final class AutoRegisterDomainProviderTest extends ApiTestCase
|
||||
$client = AutoRegisterDomainProviderTest::createClientWithCredentials(AutoRegisterDomainProviderTest::getToken(UserFactory::createOne()));
|
||||
|
||||
$mockedDomain = $this->getMockBuilder(Domain::class)->getMock();
|
||||
$mockedDomain->method('isToBeUpdated')->willReturn(false);
|
||||
|
||||
$mockedDomainRepository = $this->createMock(DomainRepository::class);
|
||||
$mockedDomainRepository->method('findOneBy')->willReturn($mockedDomain);
|
||||
|
||||
$rdapServiceMocked = $this->createMock(RDAPService::class);
|
||||
$rdapServiceMocked->method('isToBeUpdated')->willReturn(false);
|
||||
$rdapServiceMocked->expects(self::never())->method('registerDomain');
|
||||
|
||||
$container = static::getContainer();
|
||||
|
||||
Reference in New Issue
Block a user