feat: start Atom feed implementation

This commit is contained in:
Maël Gangloff
2025-04-27 12:13:06 +02:00
parent c940fba01a
commit c0dcb2f848
8 changed files with 561 additions and 117 deletions

View File

@@ -7,10 +7,28 @@ use ApiPlatform\Metadata\Get;
use App\Config\EventAction;
use App\Controller\DomainRefreshController;
use App\Repository\DomainRepository;
use DateInterval;
use DateMalformedIntervalStringException;
use DateTimeImmutable;
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 Exception;
use Laminas\Feed\Writer\Entry;
use Laminas\Feed\Writer\Source;
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;
@@ -91,11 +109,11 @@ class Domain
private Collection $nameservers;
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
private ?\DateTimeImmutable $createdAt;
private ?DateTimeImmutable $createdAt;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
#[Groups(['domain:item', 'domain:list'])]
private ?\DateTimeImmutable $updatedAt;
private ?DateTimeImmutable $updatedAt;
#[ORM\ManyToOne]
#[ORM\JoinColumn(referencedColumnName: 'tld', nullable: false)]
@@ -139,7 +157,7 @@ class Domain
$this->domainEntities = new ArrayCollection();
$this->watchLists = new ArrayCollection();
$this->nameservers = new ArrayCollection();
$this->updatedAt = new \DateTimeImmutable('now');
$this->updatedAt = new DateTimeImmutable('now');
$this->createdAt = $this->updatedAt;
$this->deleted = false;
$this->domainStatuses = new ArrayCollection();
@@ -292,7 +310,7 @@ class Domain
return $this;
}
public function getUpdatedAt(): ?\DateTimeImmutable
public function getUpdatedAt(): ?DateTimeImmutable
{
return $this->updatedAt;
}
@@ -301,7 +319,7 @@ class Domain
#[ORM\PreUpdate]
public function updateTimestamps(): static
{
$this->setUpdatedAt(new \DateTimeImmutable('now'));
$this->setUpdatedAt(new DateTimeImmutable('now'));
if (null === $this->getCreatedAt()) {
$this->setCreatedAt($this->getUpdatedAt());
}
@@ -309,17 +327,17 @@ class Domain
return $this;
}
private function setUpdatedAt(?\DateTimeImmutable $updatedAt): void
private function setUpdatedAt(?DateTimeImmutable $updatedAt): void
{
$this->updatedAt = $updatedAt;
}
public function getCreatedAt(): ?\DateTimeImmutable
public function getCreatedAt(): ?DateTimeImmutable
{
return $this->createdAt;
}
private function setCreatedAt(?\DateTimeImmutable $createdAt): void
private function setCreatedAt(?DateTimeImmutable $createdAt): void
{
$this->createdAt = $createdAt;
}
@@ -352,7 +370,7 @@ class Domain
* Determines if a domain name needs special attention.
* These domain names are those whose last event was expiration or deletion.
*
* @throws \Exception
* @throws Exception
*/
private function isToBeWatchClosely(): bool
{
@@ -363,10 +381,10 @@ class Domain
/** @var DomainEvent[] $events */
$events = $this->getEvents()
->filter(fn (DomainEvent $e) => !$e->getDeleted() && $e->getDate() <= new \DateTimeImmutable('now'))
->filter(fn(DomainEvent $e) => !$e->getDeleted() && $e->getDate() <= new DateTimeImmutable('now'))
->toArray();
usort($events, fn (DomainEvent $e1, DomainEvent $e2) => $e2->getDate() <=> $e1->getDate());
usort($events, fn(DomainEvent $e1, DomainEvent $e2) => $e2->getDate() <=> $e1->getDate());
return !empty($events) && in_array($events[0]->getAction(), self::IMPORTANT_EVENTS);
}
@@ -377,11 +395,11 @@ class Domain
* - 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
* @throws Exception
*/
public function isToBeUpdated(bool $fromUser = true, bool $intensifyLastDay = false): bool
{
$updatedAtDiff = $this->getUpdatedAt()->diff(new \DateTimeImmutable());
$updatedAtDiff = $this->getUpdatedAt()->diff(new DateTimeImmutable());
if ($updatedAtDiff->days >= 7) {
return true;
@@ -477,9 +495,9 @@ class Domain
}
/**
* @throws \DateMalformedIntervalStringException
* @throws DateMalformedIntervalStringException
*/
private function calculateDaysFromStatus(\DateTimeImmutable $now): ?int
private function calculateDaysFromStatus(DateTimeImmutable $now): ?int
{
$lastStatus = $this->getDomainStatuses()->last();
if (false === $lastStatus) {
@@ -487,16 +505,16 @@ class Domain
}
if ($this->isPendingDelete() && (
in_array('pending delete', $lastStatus->getAddStatus())
|| in_array('redemption period', $lastStatus->getDeleteStatus()))
in_array('pending delete', $lastStatus->getAddStatus())
|| in_array('redemption period', $lastStatus->getDeleteStatus()))
) {
return self::daysBetween($now, $lastStatus->getCreatedAt()->add(new \DateInterval('P'. 5 .'D')));
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 self::daysBetween($now, $lastStatus->getCreatedAt()->add(new DateInterval('P' . (30 + 5) . 'D')));
}
return null;
@@ -521,7 +539,7 @@ class Domain
}
*/
private static function daysBetween(\DateTimeImmutable $start, \DateTimeImmutable $end): int
private static function daysBetween(DateTimeImmutable $start, DateTimeImmutable $end): int
{
$interval = $start->setTime(0, 0)->diff($end->setTime(0, 0));
@@ -542,7 +560,7 @@ class Domain
}
/**
* @throws \Exception
* @throws Exception
*/
private function getRelevantDates(): array
{
@@ -561,7 +579,7 @@ class Domain
}
/**
* @throws \Exception
* @throws Exception
*/
#[Groups(['domain:item', 'domain:list'])]
public function getExpiresInDays(): ?int
@@ -570,11 +588,11 @@ class Domain
return null;
}
$now = new \DateTimeImmutable();
$now = new DateTimeImmutable();
[$expiredAt, $deletedAt] = $this->getRelevantDates();
if ($expiredAt) {
$guess = self::daysBetween($now, $expiredAt->add(new \DateInterval('P'.(45 + 30 + 5).'D')));
$guess = self::daysBetween($now, $expiredAt->add(new DateInterval('P' . (45 + 30 + 5) . 'D')));
}
if ($deletedAt) {
@@ -583,7 +601,7 @@ class Domain
return 0;
}
$guess = self::daysBetween($now, $deletedAt->add(new \DateInterval('P'. 30 .'D')));
$guess = self::daysBetween($now, $deletedAt->add(new DateInterval('P' . 30 . 'D')));
}
return self::returnExpiresIn([
@@ -591,4 +609,68 @@ class Domain
$this->calculateDaysFromStatus($now),
]);
}
/**
* @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->getDeleted())->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;
}
}