refactor: move logic in RDAPService

This commit is contained in:
Maël Gangloff
2025-10-22 15:24:29 +02:00
parent 9f71013c8e
commit 24e1c1533c
4 changed files with 133 additions and 120 deletions

View File

@@ -9,6 +9,7 @@ use App\Entity\User;
use App\Entity\WatchList; use App\Entity\WatchList;
use App\Repository\DomainEventRepository; use App\Repository\DomainEventRepository;
use App\Repository\WatchListRepository; use App\Repository\WatchListRepository;
use App\Service\RDAPService;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Eluceo\iCal\Domain\Entity\Calendar; use Eluceo\iCal\Domain\Entity\Calendar;
use Eluceo\iCal\Presentation\Component\Property; use Eluceo\iCal\Presentation\Component\Property;
@@ -29,6 +30,7 @@ class WatchListController extends AbstractController
public function __construct( public function __construct(
private readonly WatchListRepository $watchListRepository, private readonly WatchListRepository $watchListRepository,
private readonly DomainEventRepository $domainEventRepository, private readonly DomainEventRepository $domainEventRepository,
private readonly RDAPService $RDAPService,
) { ) {
} }
@@ -110,9 +112,10 @@ class WatchListController extends AbstractController
/** @var Domain $domain */ /** @var Domain $domain */
foreach ($watchList->getDomains()->getIterator() as $domain) { foreach ($watchList->getDomains()->getIterator() as $domain) {
/** @var DomainEvent|null $exp */ /** @var DomainEvent|null $exp */
$exp = $this->domainEventRepository->findLastExpirationDomainEvent($domain); $exp = $this->domainEventRepository->findLastDomainEvent($domain, 'expiration');
if (!$domain->getDeleted() && null !== $exp && !in_array($domain, $domains)) { if (!$domain->getDeleted() && null !== $exp && !in_array($domain, $domains)) {
$domain->setExpiresInDays($this->RDAPService->getExpiresInDays($domain));
$domains[] = $domain; $domains[] = $domain;
} }
} }

View File

@@ -148,6 +148,8 @@ class Domain
#[Groups(['domain:item'])] #[Groups(['domain:item'])]
private Collection $dnsKey; private Collection $dnsKey;
private ?int $expiresInDays;
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value]; private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];
private const IMPORTANT_STATUS = [ private const IMPORTANT_STATUS = [
'redemption period', 'redemption period',
@@ -507,122 +509,6 @@ class Domain
return in_array('pending delete', $this->getStatus()) && !in_array('redemption period', $this->getStatus()); return in_array('pending delete', $this->getStatus()) && !in_array('redemption period', $this->getStatus());
} }
/**
* @throws \DateMalformedIntervalStringException
*/
private function calculateDaysFromStatus(\DateTimeImmutable $now): ?int
{
$lastStatus = $this->getDomainStatuses()->first();
if (false === $lastStatus) {
return null;
}
if ($this->isPendingDelete() && (
in_array('pending delete', $lastStatus->getAddStatus())
|| in_array('redemption period', $lastStatus->getDeleteStatus()))
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'. 5 .'D')));
}
if ($this->isRedemptionPeriod()
&& in_array('redemption period', $lastStatus->getAddStatus())
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'.(30 + 5).'D')));
}
return null;
}
/*
private function calculateDaysFromEvents(\DateTimeImmutable $now): ?int
{
$lastChangedEvent = $this->getEvents()->findFirst(fn (int $i, DomainEvent $e) => !$e->getDeleted() && EventAction::LastChanged->value === $e->getAction());
if (null === $lastChangedEvent) {
return null;
}
if ($this->isRedemptionPeriod()) {
return self::daysBetween($now, $lastChangedEvent->getDate()->add(new \DateInterval('P'.(30 + 5).'D')));
}
if ($this->isPendingDelete()) {
return self::daysBetween($now, $lastChangedEvent->getDate()->add(new \DateInterval('P'. 5 .'D')));
}
return null;
}
*/
private static function daysBetween(\DateTimeImmutable $start, \DateTimeImmutable $end): int
{
$interval = $start->setTime(0, 0)->diff($end->setTime(0, 0));
return $interval->invert ? -$interval->days : $interval->days;
}
private static function returnExpiresIn(array $guesses): ?int
{
$filteredGuesses = array_filter($guesses, function ($value) {
return null !== $value;
});
if (empty($filteredGuesses)) {
return null;
}
return max(min($filteredGuesses), 0);
}
/**
* @throws \Exception
*/
private function getRelevantDates(): array
{
$expiredAt = $deletedAt = null;
foreach ($this->getEvents()->getIterator() as $event) {
if (!$event->getDeleted()) {
if ('expiration' === $event->getAction()) {
$expiredAt = $event->getDate();
} elseif ('deletion' === $event->getAction()) {
$deletedAt = $event->getDate();
}
}
}
return [$expiredAt, $deletedAt];
}
/**
* @throws \Exception
*/
#[Groups(['domain:item', 'domain:list'])]
public function getExpiresInDays(): ?int
{
if ($this->getDeleted()) {
return null;
}
$now = new \DateTimeImmutable();
[$expiredAt, $deletedAt] = $this->getRelevantDates();
if ($expiredAt) {
$guess = self::daysBetween($now, $expiredAt->add(new \DateInterval('P'.(45 + 30 + 5).'D')));
}
if ($deletedAt) {
// It has been observed that AFNIC, on the last day, adds a "deleted" event and removes the redemption period status.
if (0 === self::daysBetween($now, $deletedAt) && $this->isPendingDelete()) {
return 0;
}
$guess = self::daysBetween($now, $deletedAt->add(new \DateInterval('P'. 30 .'D')));
}
return self::returnExpiresIn([
$guess ?? null,
$this->calculateDaysFromStatus($now),
]);
}
/** /**
* @return Collection<int, DnsKey> * @return Collection<int, DnsKey>
*/ */
@@ -718,4 +604,17 @@ class Domain
return $events; return $events;
} }
#[Groups(['domain:item', 'domain:list'])]
public function getExpiresInDays(): ?int
{
return $this->expiresInDays;
}
public function setExpiresInDays(?int $expiresInDays): static
{
$this->expiresInDays = $expiresInDays;
return $this;
}
} }

View File

@@ -17,17 +17,18 @@ class DomainEventRepository extends ServiceEntityRepository
parent::__construct($registry, DomainEvent::class); parent::__construct($registry, DomainEvent::class);
} }
public function findLastExpirationDomainEvent(Domain $domain) public function findLastDomainEvent(Domain $domain, string $action)
{ {
return $this->createQueryBuilder('de') return $this->createQueryBuilder('de')
->select() ->select()
->where('de.domain = :domain') ->where('de.domain = :domain')
->andWhere('de.action = \'expiration\'') ->andWhere('de.action = :action')
->andWhere('de.deleted = FALSE') ->andWhere('de.deleted = FALSE')
->orderBy('de.date', 'DESC') ->orderBy('de.date', 'DESC')
->setMaxResults(1) ->setMaxResults(1)
->getQuery() ->getQuery()
->setParameter('domain', $domain) ->setParameter('domain', $domain)
->setParameter('action', $action)
->getOneOrNullResult(); ->getOneOrNullResult();
} }

View File

@@ -23,6 +23,7 @@ use App\Exception\UnknownRdapServerException;
use App\Repository\DomainEntityRepository; use App\Repository\DomainEntityRepository;
use App\Repository\DomainEventRepository; use App\Repository\DomainEventRepository;
use App\Repository\DomainRepository; use App\Repository\DomainRepository;
use App\Repository\DomainStatusRepository;
use App\Repository\EntityEventRepository; use App\Repository\EntityEventRepository;
use App\Repository\EntityRepository; use App\Repository\EntityRepository;
use App\Repository\IcannAccreditationRepository; use App\Repository\IcannAccreditationRepository;
@@ -79,7 +80,7 @@ class RDAPService
private readonly StatService $statService, private readonly StatService $statService,
private readonly InfluxdbService $influxService, private readonly InfluxdbService $influxService,
#[Autowire(param: 'influxdb_enabled')] #[Autowire(param: 'influxdb_enabled')]
private readonly bool $influxdbEnabled, private readonly bool $influxdbEnabled, private readonly DomainStatusRepository $domainStatusRepository,
) { ) {
} }
@@ -716,4 +717,113 @@ class RDAPService
]); ]);
} }
} }
private function calculateDaysFromStatus(Domain $domain, \DateTimeImmutable $now): ?int
{
/** @var ?DomainStatus $lastStatus */
$lastStatus = $this->domainStatusRepository->createQueryBuilder('ds')
->select()
->where('ds.domain = :domain')
->setParameter('domain', $domain)
->orderBy('ds.createdAt', 'DESC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if (null === $lastStatus) {
return null;
}
if ($domain->isPendingDelete() && (
in_array('pending delete', $lastStatus->getAddStatus())
|| in_array('redemption period', $lastStatus->getDeleteStatus()))
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'. 5 .'D')));
}
if ($domain->isRedemptionPeriod()
&& in_array('redemption period', $lastStatus->getAddStatus())
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'.(30 + 5).'D')));
}
return null;
}
private function getRelevantDates(Domain $domain): array
{
/** @var ?DomainEvent $expirationEvent */
$expirationEvent = $this->domainEventRepository->findLastDomainEvent($domain, 'expiration');
/** @var ?DomainEvent $deletionEvent */
$deletionEvent = $this->domainEventRepository->findLastDomainEvent($domain, 'deletion');
return [$expirationEvent?->getDate(), $deletionEvent?->getDate()];
}
public function getExpiresInDays(Domain $domain): ?int
{
if ($domain->getDeleted()) {
return null;
}
$now = new \DateTimeImmutable();
[$expiredAt, $deletedAt] = $this->getRelevantDates($domain);
if ($expiredAt) {
$guess = self::daysBetween($now, $expiredAt->add(new \DateInterval('P'.(45 + 30 + 5).'D')));
}
if ($deletedAt) {
// It has been observed that AFNIC, on the last day, adds a "deleted" event and removes the redemption period status.
if (0 === self::daysBetween($now, $deletedAt) && $domain->isPendingDelete()) {
return 0;
}
$guess = self::daysBetween($now, $deletedAt->add(new \DateInterval('P'. 30 .'D')));
}
return self::returnExpiresIn([
$guess ?? null,
$this->calculateDaysFromStatus($domain, $now),
]);
}
/*
private function calculateDaysFromEvents(\DateTimeImmutable $now): ?int
{
$lastChangedEvent = $this->getEvents()->findFirst(fn (int $i, DomainEvent $e) => !$e->getDeleted() && EventAction::LastChanged->value === $e->getAction());
if (null === $lastChangedEvent) {
return null;
}
if ($this->isRedemptionPeriod()) {
return self::daysBetween($now, $lastChangedEvent->getDate()->add(new \DateInterval('P'.(30 + 5).'D')));
}
if ($this->isPendingDelete()) {
return self::daysBetween($now, $lastChangedEvent->getDate()->add(new \DateInterval('P'. 5 .'D')));
}
return null;
}
*/
private static function daysBetween(\DateTimeImmutable $start, \DateTimeImmutable $end): int
{
$interval = $start->setTime(0, 0)->diff($end->setTime(0, 0));
return $interval->invert ? -$interval->days : $interval->days;
}
private static function returnExpiresIn(array $guesses): ?int
{
$filteredGuesses = array_filter($guesses, function ($value) {
return null !== $value;
});
if (empty($filteredGuesses)) {
return null;
}
return max(min($filteredGuesses), 0);
}
} }