mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
Merge branch 'feat/rss-feed'
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
"eluceo/ical": "^2.14",
|
"eluceo/ical": "^2.14",
|
||||||
"influxdata/influxdb-client-php": "^3.6",
|
"influxdata/influxdb-client-php": "^3.6",
|
||||||
"knpuniversity/oauth2-client-bundle": "^2.18",
|
"knpuniversity/oauth2-client-bundle": "^2.18",
|
||||||
|
"laminas/laminas-feed": "^2.23",
|
||||||
"lexik/jwt-authentication-bundle": "^3.1",
|
"lexik/jwt-authentication-bundle": "^3.1",
|
||||||
"metaregistrar/php-epp-client": "^1.0",
|
"metaregistrar/php-epp-client": "^1.0",
|
||||||
"nelmio/cors-bundle": "^2.5",
|
"nelmio/cors-bundle": "^2.5",
|
||||||
|
|||||||
202
composer.lock
generated
202
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "a4fba182d42fd845c37ea2a74b65566e",
|
"content-hash": "996e9f565d9f45226fe13354e8fde102",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "api-platform/core",
|
"name": "api-platform/core",
|
||||||
@@ -2153,6 +2153,206 @@
|
|||||||
},
|
},
|
||||||
"time": "2024-10-02T14:26:09+00:00"
|
"time": "2024-10-02T14:26:09+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laminas/laminas-escaper",
|
||||||
|
"version": "2.16.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laminas/laminas-escaper.git",
|
||||||
|
"reference": "9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8",
|
||||||
|
"reference": "9cf1f5317ca65b4fd5c6a3c2855e24a187b288c8",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"zendframework/zend-escaper": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"infection/infection": "^0.29.8",
|
||||||
|
"laminas/laminas-coding-standard": "~3.0.1",
|
||||||
|
"phpunit/phpunit": "^10.5.45",
|
||||||
|
"psalm/plugin-phpunit": "^0.19.2",
|
||||||
|
"vimeo/psalm": "^6.6.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laminas\\Escaper\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs",
|
||||||
|
"homepage": "https://laminas.dev",
|
||||||
|
"keywords": [
|
||||||
|
"escaper",
|
||||||
|
"laminas"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://laminas.dev/chat",
|
||||||
|
"docs": "https://docs.laminas.dev/laminas-escaper/",
|
||||||
|
"forum": "https://discourse.laminas.dev",
|
||||||
|
"issues": "https://github.com/laminas/laminas-escaper/issues",
|
||||||
|
"rss": "https://github.com/laminas/laminas-escaper/releases.atom",
|
||||||
|
"source": "https://github.com/laminas/laminas-escaper"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||||
|
"type": "community_bridge"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-17T12:40:19+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laminas/laminas-feed",
|
||||||
|
"version": "2.23.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laminas/laminas-feed.git",
|
||||||
|
"reference": "23807e692b3174750b426143bd93572b71b6739a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laminas/laminas-feed/zipball/23807e692b3174750b426143bd93572b71b6739a",
|
||||||
|
"reference": "23807e692b3174750b426143bd93572b71b6739a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-libxml": "*",
|
||||||
|
"laminas/laminas-escaper": "^2.9",
|
||||||
|
"laminas/laminas-stdlib": "^3.6",
|
||||||
|
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"laminas/laminas-servicemanager": "<3.3",
|
||||||
|
"zendframework/zend-feed": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laminas/laminas-cache": "^2.13.2 || ^3.12",
|
||||||
|
"laminas/laminas-cache-storage-adapter-memory": "^1.1.0 || ^2.3",
|
||||||
|
"laminas/laminas-coding-standard": "~2.5.0",
|
||||||
|
"laminas/laminas-db": "^2.18",
|
||||||
|
"laminas/laminas-http": "^2.19",
|
||||||
|
"laminas/laminas-servicemanager": "^3.22.1",
|
||||||
|
"laminas/laminas-validator": "^2.46",
|
||||||
|
"phpunit/phpunit": "^10.5.5",
|
||||||
|
"psalm/plugin-phpunit": "^0.19.0",
|
||||||
|
"psr/http-message": "^2.0",
|
||||||
|
"vimeo/psalm": "^5.18.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests",
|
||||||
|
"laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub",
|
||||||
|
"laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader",
|
||||||
|
"laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations",
|
||||||
|
"laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent",
|
||||||
|
"psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laminas\\Feed\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"description": "provides functionality for creating and consuming RSS and Atom feeds",
|
||||||
|
"homepage": "https://laminas.dev",
|
||||||
|
"keywords": [
|
||||||
|
"atom",
|
||||||
|
"feed",
|
||||||
|
"laminas",
|
||||||
|
"rss"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://laminas.dev/chat",
|
||||||
|
"docs": "https://docs.laminas.dev/laminas-feed/",
|
||||||
|
"forum": "https://discourse.laminas.dev",
|
||||||
|
"issues": "https://github.com/laminas/laminas-feed/issues",
|
||||||
|
"rss": "https://github.com/laminas/laminas-feed/releases.atom",
|
||||||
|
"source": "https://github.com/laminas/laminas-feed"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||||
|
"type": "community_bridge"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-10-09T10:53:30+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laminas/laminas-stdlib",
|
||||||
|
"version": "3.20.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laminas/laminas-stdlib.git",
|
||||||
|
"reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/8974a1213be42c3e2f70b2c27b17f910291ab2f4",
|
||||||
|
"reference": "8974a1213be42c3e2f70b2c27b17f910291ab2f4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"zendframework/zend-stdlib": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laminas/laminas-coding-standard": "^3.0",
|
||||||
|
"phpbench/phpbench": "^1.3.1",
|
||||||
|
"phpunit/phpunit": "^10.5.38",
|
||||||
|
"psalm/plugin-phpunit": "^0.19.0",
|
||||||
|
"vimeo/psalm": "^5.26.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laminas\\Stdlib\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"description": "SPL extensions, array utilities, error handlers, and more",
|
||||||
|
"homepage": "https://laminas.dev",
|
||||||
|
"keywords": [
|
||||||
|
"laminas",
|
||||||
|
"stdlib"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"chat": "https://laminas.dev/chat",
|
||||||
|
"docs": "https://docs.laminas.dev/laminas-stdlib/",
|
||||||
|
"forum": "https://discourse.laminas.dev",
|
||||||
|
"issues": "https://github.com/laminas/laminas-stdlib/issues",
|
||||||
|
"rss": "https://github.com/laminas/laminas-stdlib/releases.atom",
|
||||||
|
"source": "https://github.com/laminas/laminas-stdlib"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||||
|
"type": "community_bridge"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-10-29T13:46:07+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lcobucci/clock",
|
"name": "lcobucci/clock",
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ security:
|
|||||||
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/register$, roles: PUBLIC_ACCESS }
|
- { path: ^/api/register$, roles: PUBLIC_ACCESS }
|
||||||
- { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/calendar$", roles: PUBLIC_ACCESS }
|
- { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/calendar$", roles: PUBLIC_ACCESS }
|
||||||
|
- { path: "^/api/watchlists/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/rss", roles: PUBLIC_ACCESS }
|
||||||
- { path: "^/api/config$", roles: PUBLIC_ACCESS }
|
- { path: "^/api/config$", roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
|
||||||
|
|||||||
@@ -3,29 +3,23 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\Domain;
|
use App\Entity\Domain;
|
||||||
use App\Entity\DomainEntity;
|
|
||||||
use App\Entity\DomainEvent;
|
use App\Entity\DomainEvent;
|
||||||
|
use App\Entity\DomainStatus;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Entity\WatchList;
|
use App\Entity\WatchList;
|
||||||
use App\Repository\WatchListRepository;
|
use App\Repository\WatchListRepository;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Eluceo\iCal\Domain\Entity\Attendee;
|
|
||||||
use Eluceo\iCal\Domain\Entity\Calendar;
|
use Eluceo\iCal\Domain\Entity\Calendar;
|
||||||
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 Eluceo\iCal\Presentation\Component\Property;
|
use Eluceo\iCal\Presentation\Component\Property;
|
||||||
use Eluceo\iCal\Presentation\Component\Property\Value\TextValue;
|
use Eluceo\iCal\Presentation\Component\Property\Value\TextValue;
|
||||||
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
|
use Eluceo\iCal\Presentation\Factory\CalendarFactory;
|
||||||
|
use Laminas\Feed\Writer\Entry;
|
||||||
|
use Laminas\Feed\Writer\Feed;
|
||||||
use Sabre\VObject\EofException;
|
use Sabre\VObject\EofException;
|
||||||
use Sabre\VObject\InvalidDataException;
|
use Sabre\VObject\InvalidDataException;
|
||||||
use Sabre\VObject\ParseException;
|
use Sabre\VObject\ParseException;
|
||||||
use Sabre\VObject\Reader;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
@@ -76,59 +70,9 @@ class WatchListController extends AbstractController
|
|||||||
|
|
||||||
/** @var Domain $domain */
|
/** @var Domain $domain */
|
||||||
foreach ($watchList->getDomains()->getIterator() as $domain) {
|
foreach ($watchList->getDomains()->getIterator() as $domain) {
|
||||||
$attendees = [];
|
foreach ($domain->getDomainCalendarEvents() as $event) {
|
||||||
|
$calendar->addEvent($event);
|
||||||
/* @var DomainEntity $entity */
|
|
||||||
foreach ($domain->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 ($domain->getEvents()->filter(fn (DomainEvent $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $event) {
|
|
||||||
$calendar->addEvent((new Event())
|
|
||||||
->setLastModified(new Timestamp($domain->getUpdatedAt()))
|
|
||||||
->setStatus(EventStatus::CONFIRMED())
|
|
||||||
->setSummary($domain->getLdhName().': '.$event->getAction())
|
|
||||||
->addCategory(new Category($event->getAction()))
|
|
||||||
->setAttendees($attendees)
|
|
||||||
->setOccurrence(new SingleDay(new Date($event->getDate())))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$expiresInDays = $domain->getExpiresInDays();
|
|
||||||
|
|
||||||
if (null === $expiresInDays) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$calendar->addEvent((new Event())
|
|
||||||
->setLastModified(new Timestamp($domain->getUpdatedAt()))
|
|
||||||
->setStatus(EventStatus::CONFIRMED())
|
|
||||||
->setSummary($domain->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'))
|
|
||||||
)))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$calendarResponse = (new CalendarFactory())->createCalendar($calendar);
|
$calendarResponse = (new CalendarFactory())->createCalendar($calendar);
|
||||||
@@ -176,4 +120,137 @@ class WatchListController extends AbstractController
|
|||||||
|
|
||||||
return $domains;
|
return $domains;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
#[Route(
|
||||||
|
path: '/api/watchlists/{token}/rss/events',
|
||||||
|
name: 'watchlist_rss_events',
|
||||||
|
defaults: [
|
||||||
|
'_api_resource_class' => WatchList::class,
|
||||||
|
'_api_operation_name' => 'rss_events',
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function getWatchlistRssEventsFeed(string $token, Request $request): Response
|
||||||
|
{
|
||||||
|
/** @var WatchList $watchlist */
|
||||||
|
$watchlist = $this->watchListRepository->findOneBy(['token' => $token]);
|
||||||
|
|
||||||
|
$feed = (new Feed())
|
||||||
|
->setLanguage('en')
|
||||||
|
->setCopyright('Domain Watchdog makes this information available "as is," and does not guarantee its accuracy.')
|
||||||
|
->setTitle('Domain events ('.$watchlist->getName().')')
|
||||||
|
->setGenerator('Domain Watchdog - RSS Feed', null, 'https://github.com/maelgangloff/domain-watchdog')
|
||||||
|
->setDescription('The latest events for domain names in your Watchlist')
|
||||||
|
->setLink($request->getSchemeAndHttpHost().'/#/tracking/watchlist')
|
||||||
|
->setFeedLink($request->getSchemeAndHttpHost().'/api/watchlists/'.$token.'/rss/events', 'atom')
|
||||||
|
->setDateCreated($watchlist->getCreatedAt());
|
||||||
|
|
||||||
|
/** @var Domain $domain */
|
||||||
|
foreach ($watchlist->getDomains()->getIterator() as $domain) {
|
||||||
|
foreach ($this->getRssEventEntries($request->getSchemeAndHttpHost(), $domain) as $entry) {
|
||||||
|
$feed->addEntry($entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($feed->export('atom'), Response::HTTP_OK, [
|
||||||
|
'Content-Type' => 'application/atom+xml; charset=utf-8',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
#[Route(
|
||||||
|
path: '/api/watchlists/{token}/rss/status',
|
||||||
|
name: 'watchlist_rss_status',
|
||||||
|
defaults: [
|
||||||
|
'_api_resource_class' => WatchList::class,
|
||||||
|
'_api_operation_name' => 'rss_status',
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function getWatchlistRssStatusFeed(string $token, Request $request): Response
|
||||||
|
{
|
||||||
|
/** @var WatchList $watchlist */
|
||||||
|
$watchlist = $this->watchListRepository->findOneBy(['token' => $token]);
|
||||||
|
|
||||||
|
$feed = (new Feed())
|
||||||
|
->setLanguage('en')
|
||||||
|
->setCopyright('Domain Watchdog makes this information available "as is," and does not guarantee its accuracy.')
|
||||||
|
->setTitle('Domain EPP status ('.$watchlist->getName().')')
|
||||||
|
->setGenerator('Domain Watchdog - RSS Feed', null, 'https://github.com/maelgangloff/domain-watchdog')
|
||||||
|
->setDescription('The latest changes to the EPP status of the domain names in your Watchlist')
|
||||||
|
->setLink($request->getSchemeAndHttpHost().'/#/tracking/watchlist')
|
||||||
|
->setFeedLink($request->getSchemeAndHttpHost().'/api/watchlists/'.$token.'/rss/status', 'atom')
|
||||||
|
->setDateCreated($watchlist->getCreatedAt());
|
||||||
|
|
||||||
|
/** @var Domain $domain */
|
||||||
|
foreach ($watchlist->getDomains()->getIterator() as $domain) {
|
||||||
|
foreach ($this->getRssStatusEntries($request->getSchemeAndHttpHost(), $domain) as $entry) {
|
||||||
|
$feed->addEntry($entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($feed->export('atom'), Response::HTTP_OK, [
|
||||||
|
'Content-Type' => 'application/atom+xml; charset=utf-8',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function getRssEventEntries(string $baseUrl, Domain $domain): array
|
||||||
|
{
|
||||||
|
$entries = [];
|
||||||
|
foreach ($domain->getEvents()->filter(fn (DomainEvent $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $event) {
|
||||||
|
$entries[] = (new Entry())
|
||||||
|
->setId($baseUrl.'/api/domains/'.$domain->getLdhName().'#events-'.$event->getId())
|
||||||
|
->setTitle($domain->getLdhName().': '.$event->getAction().' - event update')
|
||||||
|
->setDescription('Domain name event')
|
||||||
|
->setLink($baseUrl.'/#/search/domain/'.$domain->getLdhName())
|
||||||
|
->setContent($this->render('rss/event_entry.html.twig', [
|
||||||
|
'event' => $event,
|
||||||
|
])->getContent())
|
||||||
|
->setDateModified($event->getDate())
|
||||||
|
->addAuthor(['name' => strtoupper($domain->getTld()->getTld()).' Registry']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function getRssStatusEntries(string $baseUrl, Domain $domain): array
|
||||||
|
{
|
||||||
|
$entries = [];
|
||||||
|
foreach ($domain->getDomainStatuses()->filter(fn (DomainStatus $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $domainStatus) {
|
||||||
|
$authors = [['name' => strtoupper($domain->getTld()->getTld()).' Registry']];
|
||||||
|
/** @var string $status */
|
||||||
|
foreach ([...$domainStatus->getAddStatus(), ...$domainStatus->getDeleteStatus()] as $status) {
|
||||||
|
if (str_starts_with($status, 'client')) {
|
||||||
|
$authors[] = ['name' => 'Registrar'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries[] = (new Entry())
|
||||||
|
->setId($baseUrl.'/api/domains/'.$domain->getLdhName().'#status-'.$domainStatus->getId())
|
||||||
|
->setTitle($domain->getLdhName().' - EPP status update')
|
||||||
|
->setDescription('There has been a change in the EPP status of the domain name.')
|
||||||
|
->setLink($baseUrl.'/#/search/domain/'.$domain->getLdhName())
|
||||||
|
->setContent($this->render('rss/status_entry.html.twig', [
|
||||||
|
'domainStatus' => $domainStatus,
|
||||||
|
])->getContent())
|
||||||
|
->setDateCreated($domainStatus->getCreatedAt())
|
||||||
|
->setDateModified($domainStatus->getDate())
|
||||||
|
->addCategory(['term' => $domain->getLdhName()])
|
||||||
|
->addCategory(['term' => strtoupper($domain->getTld()->getTld())])
|
||||||
|
->addAuthors($authors)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
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 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\Groups;
|
||||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||||
|
|
||||||
@@ -633,4 +645,70 @@ class Domain
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Event[]
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,52 @@ use Symfony\Component\Uid\Uuid;
|
|||||||
new Delete(
|
new Delete(
|
||||||
security: 'object.user == user'
|
security: 'object.user == user'
|
||||||
),
|
),
|
||||||
|
new Get(
|
||||||
|
routeName: 'watchlist_rss_status',
|
||||||
|
defaults: ['_format' => 'xml'],
|
||||||
|
openapiContext: [
|
||||||
|
'responses' => [
|
||||||
|
'200' => [
|
||||||
|
'description' => 'Domain EPP status RSS feed',
|
||||||
|
'content' => [
|
||||||
|
'application/atom+xml' => [
|
||||||
|
'schema' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'text',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
read: false,
|
||||||
|
deserialize: false,
|
||||||
|
serialize: false,
|
||||||
|
name: 'rss_status'
|
||||||
|
),
|
||||||
|
new Get(
|
||||||
|
routeName: 'watchlist_rss_events',
|
||||||
|
defaults: ['_format' => 'xml'],
|
||||||
|
openapiContext: [
|
||||||
|
'responses' => [
|
||||||
|
'200' => [
|
||||||
|
'description' => 'Domain events RSS feed',
|
||||||
|
'content' => [
|
||||||
|
'application/atom+xml' => [
|
||||||
|
'schema' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'format' => 'text',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
read: false,
|
||||||
|
deserialize: false,
|
||||||
|
serialize: false,
|
||||||
|
name: 'rss_events'
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)]
|
)]
|
||||||
class WatchList
|
class WatchList
|
||||||
|
|||||||
8
templates/rss/event_entry.html.twig
Normal file
8
templates/rss/event_entry.html.twig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<p>
|
||||||
|
A new event has been recorded for this domain:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Action:</strong> {{ event.action }}</li>
|
||||||
|
<li><strong>Date:</strong> {{ event.date|date('Y-m-d H:i:s') }}</li>
|
||||||
|
</ul>
|
||||||
23
templates/rss/status_entry.html.twig
Normal file
23
templates/rss/status_entry.html.twig
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<p>
|
||||||
|
The EPP status of this domain name has changed:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if domainStatus.addStatus is not empty %}
|
||||||
|
<p><strong>Added statuses:</strong></p>
|
||||||
|
<ul>
|
||||||
|
{% for status in domainStatus.addStatus %}
|
||||||
|
<li>{{ status }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if domainStatus.deleteStatus is not empty %}
|
||||||
|
<p><strong>Deleted statuses:</strong></p>
|
||||||
|
<ul>
|
||||||
|
{% for status in domainStatus.deleteStatus %}
|
||||||
|
<li>{{ status }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p><strong>Change date:</strong> {{ domainStatus.date|date('Y-m-d H:i:s') }}</p>
|
||||||
Reference in New Issue
Block a user