feat: set domain as deleted when tld is deleted

This commit is contained in:
Maël Gangloff 2025-10-08 12:39:04 +02:00
parent 4409124ba8
commit dad4f98035
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
6 changed files with 127 additions and 25 deletions

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251008094821 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add deleted_at column on tld table';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE tld ADD deleted_at DATE DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN tld.deleted_at IS \'(DC2Type:date_immutable)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE tld DROP deleted_at');
}
}

View File

@ -77,6 +77,9 @@ class Tld
#[ORM\OneToMany(targetEntity: Entity::class, mappedBy: 'tld')] #[ORM\OneToMany(targetEntity: Entity::class, mappedBy: 'tld')]
private Collection $entities; private Collection $entities;
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
private ?\DateTimeImmutable $deletedAt = null;
public function __construct() public function __construct()
{ {
$this->rdapServers = new ArrayCollection(); $this->rdapServers = new ArrayCollection();
@ -241,4 +244,16 @@ class Tld
return $this; return $this;
} }
public function getDeletedAt(): ?\DateTimeImmutable
{
return $this->deletedAt;
}
public function setDeletedAt(?\DateTimeImmutable $deletedAt): static
{
$this->deletedAt = $deletedAt;
return $this;
}
} }

View File

@ -41,6 +41,7 @@ final readonly class UpdateRdapServersHandler
try { try {
$this->RDAPService->updateTldListIANA(); $this->RDAPService->updateTldListIANA();
$this->RDAPService->updateGTldListICANN(); $this->RDAPService->updateGTldListICANN();
$this->RDAPService->updateDomainsWhenTldIsDeleted();
} catch (\Throwable $throwable) { } catch (\Throwable $throwable) {
$throws[] = $throwable; $throws[] = $throwable;
} }

View File

@ -16,6 +16,17 @@ class DomainRepository extends ServiceEntityRepository
parent::__construct($registry, Domain::class); parent::__construct($registry, Domain::class);
} }
public function findByTld(string $tld): array
{
return $this->createQueryBuilder('d')
->addSelect('events')
->leftJoin('d.events', 'events')
->where('d.tld = :tld')
->setParameter('tld', $tld)
->getQuery()
->getResult();
}
// /** // /**
// * @return Domain[] Returns an array of Domain objects // * @return Domain[] Returns an array of Domain objects
// */ // */

View File

@ -16,6 +16,18 @@ class TldRepository extends ServiceEntityRepository
parent::__construct($registry, Tld::class); parent::__construct($registry, Tld::class);
} }
/**
* @return Tld[] Returns an array of deleted Tld
*/
public function findDeleted(): array
{
return $this->createQueryBuilder('t')
->andWhere('t.deletedAt IS NOT NULL')
->orderBy('t.deletedAt', 'DESC')
->getQuery()
->getResult();
}
// /** // /**
// * @return Tld[] Returns an array of Tld objects // * @return Tld[] Returns an array of Tld objects
// */ // */

View File

@ -52,9 +52,9 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
class RDAPService class RDAPService
{ {
/* @see https://www.iana.org/domains/root/db */ /* @see https://www.iana.org/domains/root/db */
public const ISO_TLD_EXCEPTION = ['ac', 'eu', 'uk', 'su', 'tp']; private const ISO_TLD_EXCEPTION = ['ac', 'eu', 'uk', 'su', 'tp'];
public const INFRA_TLD = ['arpa']; private const INFRA_TLD = ['arpa'];
public const SPONSORED_TLD = [ private const SPONSORED_TLD = [
'aero', 'aero',
'asia', 'asia',
'cat', 'cat',
@ -70,7 +70,7 @@ class RDAPService
'travel', 'travel',
'xxx', 'xxx',
]; ];
public const TEST_TLD = [ private const TEST_TLD = [
'xn--kgbechtv', 'xn--kgbechtv',
'xn--hgbk6aj7f53bba', 'xn--hgbk6aj7f53bba',
'xn--0zwm56d', 'xn--0zwm56d',
@ -84,7 +84,7 @@ class RDAPService
'xn--hlcj6aya9esc7a', 'xn--hlcj6aya9esc7a',
]; ];
public const ENTITY_HANDLE_BLACKLIST = [ private const ENTITY_HANDLE_BLACKLIST = [
'REDACTED_FOR_PRIVACY', 'REDACTED_FOR_PRIVACY',
'ANO00-FRNIC', 'ANO00-FRNIC',
'not applicable', 'not applicable',
@ -99,6 +99,12 @@ class RDAPService
'Private', 'Private',
]; ];
private const DOMAIN_DOT = '.';
private const IANA_REGISTRAR_IDS_URL = 'https://www.iana.org/assignments/registrar-ids/registrar-ids.xml';
private const IANA_RDAP_SERVER_LIST_URL = 'https://data.iana.org/rdap/dns.json';
private const IANA_TLD_LIST_URL = 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt';
private const ICANN_GTLD_LIST_URL = 'https://www.icann.org/resources/registries/gtlds/v2/gtlds.json';
public function __construct(private HttpClientInterface $client, public function __construct(private HttpClientInterface $client,
private EntityRepository $entityRepository, private EntityRepository $entityRepository,
private DomainRepository $domainRepository, private DomainRepository $domainRepository,
@ -204,30 +210,30 @@ class RDAPService
*/ */
public function getTld(string $domain): Tld public function getTld(string $domain): Tld
{ {
if (!str_contains($domain, '.')) { if (!str_contains($domain, self::DOMAIN_DOT)) {
$tldEntity = $this->tldRepository->findOneBy(['tld' => '.']); $tldEntity = $this->tldRepository->findOneBy(['tld' => self::DOMAIN_DOT]);
if (null == $tldEntity) { if (null == $tldEntity) {
throw TldNotSupportedException::fromTld($domain); throw TldNotSupportedException::fromTld(self::DOMAIN_DOT);
} }
return $tldEntity; return $tldEntity;
} }
$lastDotPosition = strrpos($domain, '.'); $lastDotPosition = strrpos($domain, self::DOMAIN_DOT);
if (false === $lastDotPosition) { if (false === $lastDotPosition) {
throw MalformedDomainException::fromDomain($domain); throw MalformedDomainException::fromDomain($domain);
} }
$tld = self::convertToIdn(substr($domain, $lastDotPosition + 1)); $tld = self::convertToIdn(substr($domain, $lastDotPosition + 1));
$tldEntity = $this->tldRepository->findOneBy(['tld' => $tld]); $tldEntity = $this->tldRepository->findOneBy(['tld' => $tld, 'deletedAt' => null]);
if (null === $tldEntity) { if (null === $tldEntity) {
$this->logger->warning('Domain name cannot be updated because the TLD is not supported', [ $this->logger->warning('Domain name cannot be updated because the TLD is not supported', [
'ldhName' => $domain, 'ldhName' => $domain,
]); ]);
throw TldNotSupportedException::fromTld($domain); throw TldNotSupportedException::fromTld($tld);
} }
return $tldEntity; return $tldEntity;
@ -383,9 +389,14 @@ class RDAPService
*/ */
private function updateDomainEvents(Domain $domain, array $rdapData): void private function updateDomainEvents(Domain $domain, array $rdapData): void
{ {
foreach ($domain->getEvents()->getIterator() as $event) { $this->domainEventRepository->createQueryBuilder('de')
$event->setDeleted(true); ->update()
} ->set('de.deleted', ':deleted')
->where('de.domain = :domain')
->setParameter('deleted', true)
->setParameter('domain', $domain)
->getQuery()
->execute();
if (isset($rdapData['events']) && is_array($rdapData['events'])) { if (isset($rdapData['events']) && is_array($rdapData['events'])) {
foreach ($rdapData['events'] as $rdapEvent) { foreach ($rdapData['events'] as $rdapEvent) {
@ -419,11 +430,15 @@ class RDAPService
private function updateDomainEntities(Domain $domain, array $rdapData): void private function updateDomainEntities(Domain $domain, array $rdapData): void
{ {
$now = new \DateTimeImmutable(); $now = new \DateTimeImmutable();
foreach ($domain->getDomainEntities()->getIterator() as $domainEntity) {
if (null !== $domainEntity->getDeletedAt()) { $this->domainEntityRepository->createQueryBuilder('de')
$domainEntity->setDeletedAt($now); ->update()
} ->set('de.deletedAt', ':now')
} ->where('de.domain = :domain')
->andWhere('de.deletedAt IS NOT NULL')
->setParameter('now', $now)
->setParameter('domain', $domain)
->getQuery()->execute();
if (!isset($rdapData['entities']) || !is_array($rdapData['entities'])) { if (!isset($rdapData['entities']) || !is_array($rdapData['entities'])) {
return; return;
@ -758,7 +773,7 @@ class RDAPService
$this->logger->info('Start of update the RDAP server list from IANA'); $this->logger->info('Start of update the RDAP server list from IANA');
$dnsRoot = $this->client->request( $dnsRoot = $this->client->request(
'GET', 'https://data.iana.org/rdap/dns.json' 'GET', self::IANA_RDAP_SERVER_LIST_URL
)->toArray(); )->toArray();
$this->updateRDAPServers($dnsRoot); $this->updateRDAPServers($dnsRoot);
@ -771,8 +786,8 @@ class RDAPService
{ {
foreach ($dnsRoot['services'] as $service) { foreach ($dnsRoot['services'] as $service) {
foreach ($service[0] as $tld) { foreach ($service[0] as $tld) {
if ('.' === $tld && null === $this->tldRepository->findOneBy(['tld' => $tld])) { if (self::DOMAIN_DOT === $tld && null === $this->tldRepository->findOneBy(['tld' => $tld])) {
$this->em->persist((new Tld())->setTld('.')->setType(TldType::root)); $this->em->persist((new Tld())->setTld(self::DOMAIN_DOT)->setType(TldType::root));
$this->em->flush(); $this->em->flush();
} }
@ -827,7 +842,7 @@ class RDAPService
fn ($tld) => strtolower($tld), fn ($tld) => strtolower($tld),
explode(PHP_EOL, explode(PHP_EOL,
$this->client->request( $this->client->request(
'GET', 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt' 'GET', self::IANA_TLD_LIST_URL
)->getContent() )->getContent()
)); ));
array_shift($tldList); array_shift($tldList);
@ -837,6 +852,12 @@ class RDAPService
continue; continue;
} }
$this->tldRepository->createQueryBuilder('t')
->update()
->set('t.deletedAt', 'COALESCE(t.removalDate, CURRENT_TIMESTAMP())')
->where('t.tld != :tld')
->setParameter('tld', self::DOMAIN_DOT);
$tldEntity = $this->tldRepository->findOneBy(['tld' => $tld]); $tldEntity = $this->tldRepository->findOneBy(['tld' => $tld]);
if (null === $tldEntity) { if (null === $tldEntity) {
@ -858,6 +879,7 @@ class RDAPService
$tldEntity->setType(TldType::gTLD); $tldEntity->setType(TldType::gTLD);
} }
$tldEntity->setDeletedAt(null);
$this->em->persist($tldEntity); $this->em->persist($tldEntity);
} }
$this->em->flush(); $this->em->flush();
@ -874,7 +896,7 @@ class RDAPService
{ {
$this->logger->info('Start of retrieval of the list of Registrar IDs according to IANA'); $this->logger->info('Start of retrieval of the list of Registrar IDs according to IANA');
$registrarList = $this->client->request( $registrarList = $this->client->request(
'GET', 'https://www.iana.org/assignments/registrar-ids/registrar-ids.xml' 'GET', self::IANA_REGISTRAR_IDS_URL
); );
$data = new \SimpleXMLElement($registrarList->getContent()); $data = new \SimpleXMLElement($registrarList->getContent());
@ -929,7 +951,7 @@ class RDAPService
$this->logger->info('Start of retrieval of the list of gTLDs according to ICANN'); $this->logger->info('Start of retrieval of the list of gTLDs according to ICANN');
$gTldList = $this->client->request( $gTldList = $this->client->request(
'GET', 'https://www.icann.org/resources/registries/gtlds/v2/gtlds.json' 'GET', self::ICANN_GTLD_LIST_URL
)->toArray()['gTLDs']; )->toArray()['gTLDs'];
foreach ($gTldList as $gTld) { foreach ($gTldList as $gTld) {
@ -967,4 +989,13 @@ class RDAPService
$this->em->flush(); $this->em->flush();
} }
public function updateDomainsWhenTldIsDeleted(): void
{
$this->domainRepository->createQueryBuilder('d')
->update()
->set('d.deleted', ':deleted')
->where('d.tld IN (SELECT t FROM '.Tld::class.' t WHERE t.deletedAt IS NOT NULL)')
->setParameter('deleted', true)->getQuery()->execute();
}
} }