From 1f5d386b0df6571903a9f6abb206039066b7a940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Mon, 27 Oct 2025 14:06:25 +0100 Subject: [PATCH] feat: add domain reverse search on registrant name --- src/Entity/Domain.php | 19 ++++ .../FindDomainListFromEntityProvider.php | 90 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/State/FindDomainListFromEntityProvider.php diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index 4044f46..ae364c0 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -5,11 +5,14 @@ namespace App\Entity; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\QueryParameter; use App\Config\EventAction; use App\Exception\MalformedDomainException; use App\Repository\DomainRepository; use App\Service\RDAPService; use App\State\AutoRegisterDomainProvider; +use App\State\FindDomainListFromEntityProvider; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; @@ -29,6 +32,22 @@ use Symfony\Component\Serializer\Attribute\SerializedName; ] ), */ + new GetCollection( + uriTemplate: '/domains', + normalizationContext: [ + 'groups' => [ + 'domain:list', + 'tld:list', + 'event:list', + 'domain:list', + 'event:list', + ], + ], + provider: FindDomainListFromEntityProvider::class, + parameters: [ + 'registrant' => new QueryParameter(description: 'The exact name of the registrant (case insensitive)', required: true), + ] + ), new Get( uriTemplate: '/domains/{ldhName}', // Do not delete this line, otherwise Symfony interprets the TLD of the domain name as a return type normalizationContext: [ diff --git a/src/State/FindDomainListFromEntityProvider.php b/src/State/FindDomainListFromEntityProvider.php new file mode 100644 index 0000000..29dea07 --- /dev/null +++ b/src/State/FindDomainListFromEntityProvider.php @@ -0,0 +1,90 @@ +requestStack->getCurrentRequest(); + $rsm = (new ResultSetMapping()) + ->addScalarResult('handles', 'handles') + ->addScalarResult('domain_ids', 'domain_ids') + ->addScalarResult('registrant', 'registrant'); + + $handleBlacklist = join(',', array_map(fn (string $s) => "'$s'", RDAPService::ENTITY_HANDLE_BLACKLIST)); + + $sql = <<> '{}' AS fn, + jsonb_path_query_first( + e.j_card, + '$[1] ? (@[0] == "org")[3]' + ) #>> '{}' AS org + FROM entity e +) sub +JOIN domain_entity de ON de.entity_uid = sub.id +WHERE LOWER(org||fn) NOT LIKE '%redacted%' + AND LOWER(org||fn) NOT LIKE '%privacy%' + AND LOWER(org||fn) NOT LIKE '%registration private%' + AND LOWER(org||fn) NOT LIKE '%domain administrator%' + AND LOWER(org||fn) NOT LIKE '%registry super user account%' + AND LOWER(org||fn) NOT LIKE '%ano nymous%' + AND LOWER(org||fn) NOT LIKE '%by proxy%' + AND handle NOT IN ($handleBlacklist) + AND de.roles @> '["registrant"]' + AND sub.tld_id IS NOT NULL + AND (LOWER(org) = LOWER(:var) OR LOWER(fn) = LOWER(:var)) +GROUP BY COALESCE(org, fn) +HAVING COALESCE(org, fn) != '' AND COALESCE(org, fn) != '' IS NOT NULL +ORDER BY cnt DESC; +SQL; + $query = $this->em->createNativeQuery($sql, $rsm); + $query->setParameter('var', trim($request->get('registrant'))); + + $result = $query->getOneOrNullResult(); + + if (!$result) { + return null; + } + + $domainList = array_filter(explode(',', trim($result['domain_ids'], '{}'))); + + if (empty($domainList)) { + return []; + } + + return $this->em->getRepository(Domain::class) + ->createQueryBuilder('d') + ->where('d.ldhName IN (:list)') + ->setParameter('list', $domainList) + ->getQuery() + ->getResult(); + } +}