2024-07-10 23:30:59 +02:00
< ? php
namespace App\Entity ;
2025-10-14 17:40:48 +02:00
use ApiPlatform\Metadata\ApiProperty ;
2024-07-17 00:19:27 +02:00
use ApiPlatform\Metadata\ApiResource ;
use ApiPlatform\Metadata\Get ;
2025-10-27 14:06:25 +01:00
use ApiPlatform\Metadata\GetCollection ;
use ApiPlatform\Metadata\QueryParameter ;
2024-12-07 20:08:29 +01:00
use App\Config\EventAction ;
2025-10-16 14:16:58 +02:00
use App\Exception\MalformedDomainException ;
2024-07-10 23:30:59 +02:00
use App\Repository\DomainRepository ;
2025-05-21 13:14:38 +02:00
use App\Service\RDAPService ;
2025-05-30 13:55:44 +02:00
use App\State\AutoRegisterDomainProvider ;
2025-10-27 17:40:31 +01:00
use App\State\FindDomainCollectionFromEntityProvider ;
2024-07-10 23:30:59 +02:00
use Doctrine\Common\Collections\ArrayCollection ;
use Doctrine\Common\Collections\Collection ;
use Doctrine\DBAL\Types\Types ;
use Doctrine\ORM\Mapping as ORM ;
2024-07-17 00:19:27 +02:00
use Symfony\Component\Serializer\Attribute\Groups ;
2024-07-21 02:25:37 +02:00
use Symfony\Component\Serializer\Attribute\SerializedName ;
2024-07-10 23:30:59 +02:00
#[ORM\Entity(repositoryClass: DomainRepository::class)]
2024-07-17 00:19:27 +02:00
#[ApiResource(
operations : [
2025-10-27 14:06:25 +01:00
new GetCollection (
uriTemplate : '/domains' ,
2025-12-07 17:01:21 +01:00
openapiContext : [
'summary' => 'Reverse Domain lookup' ,
'description' => 'This experimental endpoint allows listing domain names that meet a strict condition.' ,
],
2025-10-27 14:06:25 +01:00
normalizationContext : [
'groups' => [
'domain:list' ,
'tld:list' ,
'event:list' ,
'event:list' ,
],
],
2025-10-27 17:40:31 +01:00
provider : FindDomainCollectionFromEntityProvider :: class ,
2025-10-27 14:06:25 +01:00
parameters : [
2025-11-09 02:16:53 +01:00
'registrant' => new QueryParameter (
description : 'The exact name of the registrant contact (case insensitive)' ,
required : false
),
'administrative' => new QueryParameter (
description : 'The exact name of the administrative contact (case insensitive)' ,
required : false
),
2025-10-27 14:06:25 +01:00
]
),
2024-07-17 00:19:27 +02:00
new Get (
2024-08-02 23:24:52 +02:00
uriTemplate : '/domains/{ldhName}' , // Do not delete this line, otherwise Symfony interprets the TLD of the domain name as a return type
2025-12-07 17:01:21 +01:00
openapiContext : [
'summary' => 'Searching for a Domain' ,
'description' => 'This endpoint allows you to perform an RDAP lookup. The query is sent to the relevant registry. When the RDAP response is received, the domain name information is stored in a database for historical and cache purposes.' ,
],
2024-07-17 14:00:46 +02:00
normalizationContext : [
'groups' => [
'domain:item' ,
'event:list' ,
'domain-entity:entity' ,
2024-07-18 03:03:46 +02:00
'nameserver-entity:nameserver' ,
2024-07-19 22:58:50 +02:00
'nameserver-entity:entity' ,
2024-08-02 23:24:52 +02:00
'tld:item' ,
2025-08-26 16:18:29 +02:00
'ds:list' ,
2024-08-02 23:24:52 +02:00
],
2024-07-17 18:18:11 +02:00
],
2025-11-06 14:27:12 +01:00
parameters : [
2025-11-15 10:59:44 +01:00
'forced' => new QueryParameter ( schema : [ 'type' => 'boolean' ], description : 'Force an RDAP request. If an update is already in progress, this parameter is ignored and the stored domain is returned.' , required : false ),
2025-11-06 14:27:12 +01:00
],
2024-08-02 23:24:52 +02:00
),
2025-05-30 13:55:44 +02:00
],
2025-12-08 18:18:33 +01:00
provider : AutoRegisterDomainProvider :: class ,
2024-07-17 00:19:27 +02:00
)]
2024-07-10 23:30:59 +02:00
class Domain
{
2024-07-13 12:49:11 +02:00
#[ORM\Id]
2024-07-10 23:30:59 +02:00
#[ORM\Column(length: 255)]
2024-07-27 20:44:10 +02:00
#[Groups(['domain:item', 'domain:list', 'watchlist:item', 'watchlist:list'])]
2024-07-11 17:01:16 +02:00
private ? string $ldhName = null ;
2024-07-10 23:30:59 +02:00
2024-07-23 01:34:59 +02:00
#[ORM\Column(length: 255, nullable: true)]
2024-07-18 13:40:49 +02:00
#[Groups(['domain:item', 'domain:list', 'watchlist:item'])]
2024-07-10 23:30:59 +02:00
private ? string $handle = null ;
/**
2024-07-11 17:01:16 +02:00
* @ var Collection < int , DomainEvent >
2024-07-10 23:30:59 +02:00
*/
2024-07-12 00:50:30 +02:00
#[ORM\OneToMany(targetEntity: DomainEvent::class, mappedBy: 'domain', cascade: ['persist'], orphanRemoval: true)]
2025-04-02 18:36:35 +02:00
#[Groups(['domain:item', 'domain:list', 'watchlist:list'])]
2025-10-14 17:40:48 +02:00
#[ApiProperty(
openapiContext : [
'type' => 'array' ,
]
)]
2024-07-10 23:30:59 +02:00
private Collection $events ;
/**
* @ var Collection < int , DomainEntity >
*/
2024-07-12 00:50:30 +02:00
#[ORM\OneToMany(targetEntity: DomainEntity::class, mappedBy: 'domain', cascade: ['persist'], orphanRemoval: true)]
2025-10-15 23:01:31 +02:00
#[Groups(['domain:item', 'watchlist:item'])]
2024-07-21 02:25:37 +02:00
#[SerializedName('entities')]
2024-07-10 23:30:59 +02:00
private Collection $domainEntities ;
2025-10-16 22:21:08 +02:00
#[ORM\Column(type: Types::JSON, nullable: true)]
2024-12-18 19:40:34 +01:00
#[Groups(['domain:item', 'domain:list', 'watchlist:item', 'watchlist:list'])]
2024-07-11 13:15:04 +02:00
private array $status = [];
2024-07-11 22:20:20 +02:00
/**
2025-10-25 17:26:56 +02:00
* @ var Collection < int , Watchlist >
2024-07-11 22:20:20 +02:00
*/
2025-10-25 17:26:56 +02:00
#[ORM\ManyToMany(targetEntity: Watchlist::class, mappedBy: 'domains', cascade: ['persist'])]
private Collection $watchlists ;
2024-07-11 22:20:20 +02:00
2024-07-11 17:01:16 +02:00
/**
* @ var Collection < int , Nameserver >
*/
2024-07-12 00:50:30 +02:00
#[ORM\ManyToMany(targetEntity: Nameserver::class, inversedBy: 'domains', cascade: ['persist'])]
2024-07-11 22:20:20 +02:00
#[ORM\JoinTable(name: 'domain_nameservers',
2024-07-13 15:52:52 +02:00
joinColumns : [ new ORM\JoinColumn ( name : 'domain_ldh_name' , referencedColumnName : 'ldh_name' )],
2024-07-13 00:22:17 +02:00
inverseJoinColumns : [ new ORM\JoinColumn ( name : 'nameserver_ldh_name' , referencedColumnName : 'ldh_name' )]
2024-07-11 22:20:20 +02:00
)]
2025-10-15 23:01:31 +02:00
#[Groups(['domain:item', 'watchlist:item'])]
2024-07-11 17:01:16 +02:00
private Collection $nameservers ;
2024-07-17 19:29:43 +02:00
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
2024-09-04 18:33:02 +02:00
private ? \DateTimeImmutable $createdAt ;
2024-07-17 19:29:43 +02:00
2024-09-04 18:33:02 +02:00
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
2024-09-09 11:31:33 +02:00
#[Groups(['domain:item', 'domain:list'])]
2024-09-04 18:33:02 +02:00
private ? \DateTimeImmutable $updatedAt ;
2024-07-17 19:29:43 +02:00
2024-07-19 18:59:21 +02:00
#[ORM\ManyToOne]
#[ORM\JoinColumn(referencedColumnName: 'tld', nullable: false)]
2024-09-09 11:31:33 +02:00
#[Groups(['domain:item', 'domain:list'])]
2024-07-19 18:59:21 +02:00
private ? Tld $tld = null ;
2024-09-01 21:26:07 +02:00
#[ORM\Column(nullable: false)]
2024-12-18 19:40:34 +01:00
#[Groups(['domain:item', 'domain:list', 'watchlist:item', 'watchlist:list'])]
2024-09-01 21:26:07 +02:00
private ? bool $deleted ;
2024-07-25 16:19:57 +02:00
2024-12-29 13:47:19 +01:00
#[Groups(['domain:item'])]
private ? RdapServer $rdapServer ;
2024-12-12 22:56:35 +01:00
/**
* @ var Collection < int , DomainStatus >
*/
#[ORM\OneToMany(targetEntity: DomainStatus::class, mappedBy: 'domain', orphanRemoval: true)]
2024-12-22 22:13:27 +01:00
#[Groups(['domain:item'])]
#[SerializedName('oldStatus')]
2024-12-12 22:56:35 +01:00
private Collection $domainStatuses ;
2025-02-18 01:29:29 +01:00
#[ORM\Column(nullable: false, options: ['default' => false])]
2025-01-01 02:42:51 +01:00
#[Groups(['domain:item', 'domain:list'])]
2025-10-22 18:22:36 +02:00
private bool $delegationSigned = false ;
2024-12-30 14:31:31 +01:00
2025-08-26 16:18:29 +02:00
/**
* @ var Collection < int , DnsKey >
*/
#[ORM\OneToMany(targetEntity: DnsKey::class, mappedBy: 'domain', orphanRemoval: true)]
#[Groups(['domain:item'])]
private Collection $dnsKey ;
2025-10-22 15:24:29 +02:00
private ? int $expiresInDays ;
2025-10-31 13:40:09 +01:00
/**
* @ var Collection < int , DomainPurchase >
*/
#[ORM\OneToMany(targetEntity: DomainPurchase::class, mappedBy: 'domain', orphanRemoval: true)]
private Collection $domainPurchases ;
2024-12-07 20:08:29 +01:00
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' ,
];
2024-07-10 23:30:59 +02:00
public function __construct ()
{
$this -> events = new ArrayCollection ();
$this -> domainEntities = new ArrayCollection ();
2025-10-25 17:26:56 +02:00
$this -> watchlists = new ArrayCollection ();
2024-07-11 17:01:16 +02:00
$this -> nameservers = new ArrayCollection ();
2024-08-02 23:24:52 +02:00
$this -> updatedAt = new \DateTimeImmutable ( 'now' );
2024-12-13 16:31:06 +01:00
$this -> createdAt = $this -> updatedAt ;
2024-07-25 16:19:57 +02:00
$this -> deleted = false ;
2024-12-12 22:56:35 +01:00
$this -> domainStatuses = new ArrayCollection ();
2025-08-26 16:18:29 +02:00
$this -> dnsKey = new ArrayCollection ();
2025-10-31 13:40:09 +01:00
$this -> domainPurchases = new ArrayCollection ();
2024-07-10 23:30:59 +02:00
}
2024-07-11 17:01:16 +02:00
public function getLdhName () : ? string
2024-07-10 23:30:59 +02:00
{
2024-07-11 17:01:16 +02:00
return $this -> ldhName ;
2024-07-10 23:30:59 +02:00
}
2025-10-16 14:16:58 +02:00
/**
* @ throws MalformedDomainException
*/
2024-07-11 17:01:16 +02:00
public function setLdhName ( string $ldhName ) : static
2024-07-10 23:30:59 +02:00
{
2025-05-21 13:14:38 +02:00
$this -> ldhName = RDAPService :: convertToIdn ( $ldhName );
2024-07-10 23:30:59 +02:00
return $this ;
}
public function getHandle () : ? string
{
return $this -> handle ;
}
public function setHandle ( string $handle ) : static
{
$this -> handle = $handle ;
return $this ;
}
/**
2024-07-11 17:01:16 +02:00
* @ return Collection < int , DomainEvent >
2024-07-10 23:30:59 +02:00
*/
public function getEvents () : Collection
{
return $this -> events ;
}
2024-07-11 17:01:16 +02:00
public function addEvent ( DomainEvent $event ) : static
2024-07-10 23:30:59 +02:00
{
if ( ! $this -> events -> contains ( $event )) {
$this -> events -> add ( $event );
$event -> setDomain ( $this );
}
return $this ;
}
2024-07-11 17:01:16 +02:00
public function removeEvent ( DomainEvent $event ) : static
2024-07-10 23:30:59 +02:00
{
if ( $this -> events -> removeElement ( $event )) {
// set the owning side to null (unless already changed)
if ( $event -> getDomain () === $this ) {
$event -> setDomain ( null );
}
}
return $this ;
}
/**
* @ return Collection < int , DomainEntity >
*/
public function getDomainEntities () : Collection
{
return $this -> domainEntities ;
}
public function addDomainEntity ( DomainEntity $domainEntity ) : static
{
if ( ! $this -> domainEntities -> contains ( $domainEntity )) {
$this -> domainEntities -> add ( $domainEntity );
$domainEntity -> setDomain ( $this );
}
return $this ;
}
public function removeDomainEntity ( DomainEntity $domainEntity ) : static
{
if ( $this -> domainEntities -> removeElement ( $domainEntity )) {
// set the owning side to null (unless already changed)
if ( $domainEntity -> getDomain () === $this ) {
$domainEntity -> setDomain ( null );
}
}
return $this ;
}
2024-07-11 13:15:04 +02:00
public function getStatus () : array
{
return $this -> status ;
}
public function setStatus ( array $status ) : static
{
$this -> status = $status ;
return $this ;
}
2024-07-11 17:01:16 +02:00
2024-07-11 22:20:20 +02:00
/**
2025-10-25 17:26:56 +02:00
* @ return Collection < int , Watchlist >
2024-07-11 22:20:20 +02:00
*/
2025-10-25 17:26:56 +02:00
public function getWatchlists () : Collection
2024-07-11 22:20:20 +02:00
{
2025-10-25 17:26:56 +02:00
return $this -> watchlists ;
2024-07-11 22:20:20 +02:00
}
2025-10-25 17:26:56 +02:00
public function addWatchlists ( Watchlist $watchlist ) : static
2024-07-11 22:20:20 +02:00
{
2025-10-25 17:26:56 +02:00
if ( ! $this -> watchlists -> contains ( $watchlist )) {
$this -> watchlists -> add ( $watchlist );
$watchlist -> addDomain ( $this );
2024-07-11 22:20:20 +02:00
}
return $this ;
}
2025-10-25 17:26:56 +02:00
public function removeWatchlists ( Watchlist $watchlist ) : static
2024-07-11 22:20:20 +02:00
{
2025-10-25 17:26:56 +02:00
if ( $this -> watchlists -> removeElement ( $watchlist )) {
$watchlist -> removeDomain ( $this );
2024-07-11 22:20:20 +02:00
}
return $this ;
}
2024-07-11 17:01:16 +02:00
/**
* @ return Collection < int , Nameserver >
*/
public function getNameservers () : Collection
{
return $this -> nameservers ;
}
public function addNameserver ( Nameserver $nameserver ) : static
{
if ( ! $this -> nameservers -> contains ( $nameserver )) {
$this -> nameservers -> add ( $nameserver );
}
return $this ;
}
public function removeNameserver ( Nameserver $nameserver ) : static
{
$this -> nameservers -> removeElement ( $nameserver );
return $this ;
}
2024-07-17 19:29:43 +02:00
2024-08-02 23:24:52 +02:00
public function getUpdatedAt () : ? \DateTimeImmutable
2024-07-17 19:29:43 +02:00
{
return $this -> updatedAt ;
}
#[ORM\PrePersist]
#[ORM\PreUpdate]
2024-12-20 22:25:41 +01:00
public function updateTimestamps () : static
2024-07-17 19:29:43 +02:00
{
2024-08-02 23:24:52 +02:00
$this -> setUpdatedAt ( new \DateTimeImmutable ( 'now' ));
if ( null === $this -> getCreatedAt ()) {
2024-12-13 16:31:06 +01:00
$this -> setCreatedAt ( $this -> getUpdatedAt ());
2024-07-17 19:29:43 +02:00
}
2024-12-20 22:25:41 +01:00
return $this ;
2024-07-17 19:29:43 +02:00
}
2025-10-25 21:47:11 +02:00
public function setUpdatedAt ( ? \DateTimeImmutable $updatedAt ) : void
2024-07-17 19:29:43 +02:00
{
$this -> updatedAt = $updatedAt ;
}
2024-08-02 23:24:52 +02:00
public function getCreatedAt () : ? \DateTimeImmutable
2024-07-17 19:29:43 +02:00
{
return $this -> createdAt ;
}
2024-08-02 23:24:52 +02:00
private function setCreatedAt ( ? \DateTimeImmutable $createdAt ) : void
2024-07-17 19:29:43 +02:00
{
$this -> createdAt = $createdAt ;
}
2024-07-19 18:59:21 +02:00
public function getTld () : ? Tld
{
return $this -> tld ;
}
public function setTld ( ? Tld $tld ) : static
{
$this -> tld = $tld ;
return $this ;
}
2024-07-25 16:19:57 +02:00
public function getDeleted () : ? bool
{
return $this -> deleted ;
}
public function setDeleted ( ? bool $deleted ) : static
{
$this -> deleted = $deleted ;
return $this ;
}
2024-12-07 20:08:29 +01:00
/**
* Determines if a domain name needs special attention .
* These domain names are those whose last event was expiration or deletion .
*
* @ throws \Exception
*/
2025-10-22 15:58:20 +02:00
public function isToBeWatchClosely () : bool
2024-12-07 20:08:29 +01:00
{
$status = $this -> getStatus ();
if (( ! empty ( $status ) && count ( array_intersect ( $status , self :: IMPORTANT_STATUS ))) || $this -> getDeleted ()) {
return true ;
}
/** @var DomainEvent[] $events */
$events = $this -> getEvents ()
2024-12-07 20:22:08 +01:00
-> filter ( fn ( DomainEvent $e ) => ! $e -> getDeleted () && $e -> getDate () <= new \DateTimeImmutable ( 'now' ))
2024-12-07 20:08:29 +01:00
-> toArray ();
usort ( $events , fn ( DomainEvent $e1 , DomainEvent $e2 ) => $e2 -> getDate () <=> $e1 -> getDate ());
return ! empty ( $events ) && in_array ( $events [ 0 ] -> getAction (), self :: IMPORTANT_EVENTS );
}
2024-12-12 22:56:35 +01:00
/**
* @ return Collection < int , DomainStatus >
*/
public function getDomainStatuses () : Collection
{
return $this -> domainStatuses ;
}
public function addDomainStatus ( DomainStatus $domainStatus ) : static
{
if ( ! $this -> domainStatuses -> contains ( $domainStatus )) {
$this -> domainStatuses -> add ( $domainStatus );
$domainStatus -> setDomain ( $this );
}
return $this ;
}
public function removeDomainStatus ( DomainStatus $domainStatus ) : static
{
if ( $this -> domainStatuses -> removeElement ( $domainStatus )) {
// set the owning side to null (unless already changed)
if ( $domainStatus -> getDomain () === $this ) {
$domainStatus -> setDomain ( null );
}
}
return $this ;
}
2024-12-29 13:47:19 +01:00
public function getRdapServer () : ? RdapServer
{
return $this -> rdapServer ;
}
2024-12-30 14:31:31 +01:00
public function setRdapServer ( ? RdapServer $rdapServer ) : static
2024-12-29 13:47:19 +01:00
{
$this -> rdapServer = $rdapServer ;
2024-12-30 14:31:31 +01:00
return $this ;
}
public function isDelegationSigned () : ? bool
{
return $this -> delegationSigned ;
}
public function setDelegationSigned ( bool $delegationSigned ) : static
{
$this -> delegationSigned = $delegationSigned ;
return $this ;
2024-12-29 13:47:19 +01:00
}
2025-01-01 22:00:05 +01:00
2025-01-17 12:39:50 +01:00
public function isRedemptionPeriod () : bool
{
return in_array ( 'redemption period' , $this -> getStatus ());
}
2025-01-19 13:17:26 +01:00
public function isPendingDelete () : bool
{
return in_array ( 'pending delete' , $this -> getStatus ()) && ! in_array ( 'redemption period' , $this -> getStatus ());
}
2025-08-26 16:18:29 +02:00
/**
* @ return Collection < int , DnsKey >
*/
public function getDnsKey () : Collection
{
return $this -> dnsKey ;
}
public function addDnsKey ( DnsKey $dnsKey ) : static
{
if ( ! $this -> dnsKey -> contains ( $dnsKey )) {
$this -> dnsKey -> add ( $dnsKey );
$dnsKey -> setDomain ( $this );
}
return $this ;
}
public function removeDnsKey ( DnsKey $dnsKey ) : static
{
if ( $this -> dnsKey -> removeElement ( $dnsKey )) {
// set the owning side to null (unless already changed)
if ( $dnsKey -> getDomain () === $this ) {
$dnsKey -> setDomain ( null );
}
}
return $this ;
}
2025-08-26 16:46:05 +02:00
2025-10-22 15:24:29 +02:00
#[Groups(['domain:item', 'domain:list'])]
public function getExpiresInDays () : ? int
{
return $this -> expiresInDays ;
}
public function setExpiresInDays ( ? int $expiresInDays ) : static
{
$this -> expiresInDays = $expiresInDays ;
return $this ;
}
2025-10-31 13:40:09 +01:00
/**
* @ return Collection < int , DomainPurchase >
*/
public function getDomainPurchases () : Collection
{
return $this -> domainPurchases ;
}
public function addDomainPurchase ( DomainPurchase $domainPurchase ) : static
{
if ( ! $this -> domainPurchases -> contains ( $domainPurchase )) {
$this -> domainPurchases -> add ( $domainPurchase );
$domainPurchase -> setDomain ( $this );
}
return $this ;
}
public function removeDomainPurchase ( DomainPurchase $domainPurchase ) : static
{
if ( $this -> domainPurchases -> removeElement ( $domainPurchase )) {
// set the owning side to null (unless already changed)
if ( $domainPurchase -> getDomain () === $this ) {
$domainPurchase -> setDomain ( null );
}
}
return $this ;
}
#[Groups(['domain:item', 'domain:list'])]
public function getPurchaseCount () : ? int
{
return $this -> domainPurchases -> count ();
}
2024-07-10 23:30:59 +02:00
}