2024-07-18 13:40:49 +02:00
< ? php
namespace App\Controller ;
2024-08-19 16:34:08 +02:00
use App\Entity\Connector ;
2024-08-03 17:55:39 +02:00
use App\Entity\Domain ;
2024-08-01 14:37:23 +02:00
use App\Entity\DomainEntity ;
use App\Entity\DomainEvent ;
2024-07-18 13:40:49 +02:00
use App\Entity\User ;
use App\Entity\WatchList ;
2024-08-16 23:23:51 +02:00
use App\Notifier\TestChatNotification ;
2024-08-01 14:37:23 +02:00
use App\Repository\WatchListRepository ;
2024-08-28 17:35:32 +02:00
use App\Service\ChatNotificationService ;
2024-09-18 13:37:07 +02:00
use App\Service\Connector\AbstractProvider ;
2024-07-18 13:40:49 +02:00
use Doctrine\Common\Collections\Collection ;
use Doctrine\ORM\EntityManagerInterface ;
2024-08-15 03:04:31 +02:00
use Doctrine\ORM\Exception\ORMException ;
2024-08-01 14:37:23 +02:00
use Eluceo\iCal\Domain\Entity\Attendee ;
use Eluceo\iCal\Domain\Entity\Calendar ;
use Eluceo\iCal\Domain\Entity\Event ;
2024-08-03 17:55:39 +02:00
use Eluceo\iCal\Domain\Enum\EventStatus ;
2024-08-01 14:37:23 +02:00
use Eluceo\iCal\Domain\ValueObject\Category ;
use Eluceo\iCal\Domain\ValueObject\Date ;
use Eluceo\iCal\Domain\ValueObject\EmailAddress ;
use Eluceo\iCal\Domain\ValueObject\SingleDay ;
2024-08-03 17:55:39 +02:00
use Eluceo\iCal\Domain\ValueObject\Timestamp ;
2024-08-03 18:05:05 +02:00
use Eluceo\iCal\Presentation\Component\Property ;
use Eluceo\iCal\Presentation\Component\Property\Value\TextValue ;
2024-08-01 14:37:23 +02:00
use Eluceo\iCal\Presentation\Factory\CalendarFactory ;
2024-08-04 14:45:27 +02:00
use Psr\Log\LoggerInterface ;
2024-08-01 14:37:23 +02:00
use Sabre\VObject\EofException ;
use Sabre\VObject\InvalidDataException ;
use Sabre\VObject\ParseException ;
use Sabre\VObject\Reader ;
2024-07-18 13:40:49 +02:00
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ;
2024-09-25 14:02:40 +02:00
use Symfony\Component\DependencyInjection\Attribute\Autowire ;
use Symfony\Component\DependencyInjection\ContainerInterface ;
2024-07-18 13:40:49 +02:00
use Symfony\Component\HttpFoundation\Request ;
2024-08-01 14:37:23 +02:00
use Symfony\Component\HttpFoundation\Response ;
2024-08-04 22:00:20 +02:00
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException ;
2024-08-16 23:23:51 +02:00
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException ;
2024-07-18 13:40:49 +02:00
use Symfony\Component\Routing\Attribute\Route ;
use Symfony\Component\Serializer\SerializerInterface ;
class WatchListController extends AbstractController
{
public function __construct (
2024-08-02 23:24:52 +02:00
private readonly SerializerInterface $serializer ,
2024-08-01 14:37:23 +02:00
private readonly EntityManagerInterface $em ,
2024-08-04 14:45:27 +02:00
private readonly WatchListRepository $watchListRepository ,
2024-08-23 21:19:34 +02:00
private readonly LoggerInterface $logger ,
2024-09-25 14:02:40 +02:00
private readonly ChatNotificationService $chatNotificationService ,
#[Autowire(service: 'service_container')]
private ContainerInterface $locator
2024-08-02 23:24:52 +02:00
) {
2024-07-18 13:40:49 +02:00
}
2024-07-29 16:01:32 +02:00
/**
2024-08-02 23:24:52 +02:00
* @ throws \Exception
2024-07-29 16:01:32 +02:00
*/
2024-07-18 13:40:49 +02:00
#[Route(
path : '/api/watchlists' ,
name : 'watchlist_create' ,
defaults : [
'_api_resource_class' => WatchList :: class ,
'_api_operation_name' => 'create' ,
],
methods : [ 'POST' ]
)]
public function createWatchList ( Request $request ) : WatchList
{
$watchList = $this -> serializer -> deserialize ( $request -> getContent (), WatchList :: class , 'json' , [ 'groups' => 'watchlist:create' ]);
2024-08-04 22:00:20 +02:00
2024-08-03 00:06:38 +02:00
/** @var User $user */
$user = $this -> getUser ();
$watchList -> setUser ( $user );
2024-07-29 16:01:32 +02:00
2024-08-04 22:00:20 +02:00
/*
* In the limited version , we do not want a user to be able to register the same domain more than once in their watchlists .
* This policy guarantees the equal probability of obtaining a domain name if it is requested by several users .
*/
if ( $this -> getParameter ( 'limited_features' )) {
2024-08-15 04:06:35 +02:00
if ( $watchList -> getDomains () -> count () > ( int ) $this -> getParameter ( 'limit_max_watchlist_domains' )) {
2024-08-18 18:25:11 +02:00
$this -> logger -> notice ( 'User {username} tried to create a Watchlist. The maximum number of domains has been reached.' , [
2024-08-07 14:31:44 +02:00
'username' => $user -> getUserIdentifier (),
]);
throw new AccessDeniedHttpException ( 'You have exceeded the maximum number of domain names allowed in this Watchlist' );
}
$userWatchLists = $user -> getWatchLists ();
if ( $userWatchLists -> count () >= ( int ) $this -> getParameter ( 'limit_max_watchlist' )) {
2024-08-16 14:19:59 +02:00
$this -> logger -> notice ( 'User {username} tried to create a Watchlist. The maximum number of Watchlists has been reached' , [
2024-08-07 14:31:44 +02:00
'username' => $user -> getUserIdentifier (),
]);
throw new AccessDeniedHttpException ( 'You have exceeded the maximum number of Watchlists allowed' );
}
2024-08-04 22:00:20 +02:00
/** @var Domain[] $trackedDomains */
2024-08-07 14:31:44 +02:00
$trackedDomains = $userWatchLists -> reduce ( fn ( array $acc , WatchList $watchList ) => [ ... $acc , ... $watchList -> getDomains () -> toArray ()], []);
2024-08-04 22:00:20 +02:00
/** @var Domain $domain */
foreach ( $watchList -> getDomains () -> getIterator () as $domain ) {
if ( in_array ( $domain , $trackedDomains )) {
2024-08-16 14:19:59 +02:00
$ldhName = $domain -> getLdhName ();
$this -> logger -> notice ( 'User {username} tried to create a watchlist with domain name {ldhName}. It is forbidden to register the same domain name twice with limited mode' , [
2024-08-04 22:00:20 +02:00
'username' => $user -> getUserIdentifier (),
2024-08-16 14:19:59 +02:00
'ldhName' => $ldhName ,
2024-08-04 22:00:20 +02:00
]);
2024-08-16 14:19:59 +02:00
throw new AccessDeniedHttpException ( " It is forbidden to register the same domain name twice in your watchlists with limited mode ( $ldhName ) " );
2024-08-04 22:00:20 +02:00
}
}
2024-08-18 18:25:11 +02:00
if ( null !== $watchList -> getWebhookDsn () && count ( $watchList -> getWebhookDsn ()) > ( int ) $this -> getParameter ( 'limit_max_watchlist_webhooks' )) {
$this -> logger -> notice ( 'User {username} tried to create a Watchlist. The maximum number of webhooks has been reached.' , [
'username' => $user -> getUserIdentifier (),
]);
throw new AccessDeniedHttpException ( 'You have exceeded the maximum number of webhooks allowed in this Watchlist' );
}
2024-08-04 22:00:20 +02:00
}
2024-08-28 17:35:32 +02:00
$this -> chatNotificationService -> sendChatNotification ( $watchList , new TestChatNotification ());
2024-08-19 16:34:08 +02:00
$this -> verifyConnector ( $watchList , $watchList -> getConnector ());
2024-08-18 18:30:01 +02:00
2024-08-15 03:42:41 +02:00
$this -> logger -> info ( 'User {username} registers a Watchlist ({token}).' , [
2024-08-04 14:45:27 +02:00
'username' => $user -> getUserIdentifier (),
2024-08-04 22:00:20 +02:00
'token' => $watchList -> getToken (),
2024-08-04 14:45:27 +02:00
]);
2024-07-18 13:40:49 +02:00
$this -> em -> persist ( $watchList );
$this -> em -> flush ();
return $watchList ;
}
2024-08-14 23:23:32 +02:00
#[Route(
path : '/api/watchlists' ,
2024-08-15 03:04:31 +02:00
name : 'watchlist_get_all_mine' ,
2024-08-14 23:23:32 +02:00
defaults : [
'_api_resource_class' => WatchList :: class ,
2024-08-15 03:04:31 +02:00
'_api_operation_name' => 'get_all_mine' ,
2024-08-14 23:23:32 +02:00
],
2024-08-15 03:04:31 +02:00
methods : [ 'GET' ]
2024-08-14 23:23:32 +02:00
)]
2024-08-15 03:04:31 +02:00
public function getWatchLists () : Collection
2024-08-14 23:23:32 +02:00
{
2024-08-15 03:04:31 +02:00
/** @var User $user */
2024-08-14 23:23:32 +02:00
$user = $this -> getUser ();
2024-08-15 03:04:31 +02:00
return $user -> getWatchLists ();
2024-08-14 23:23:32 +02:00
}
2024-08-04 22:00:20 +02:00
2024-08-19 16:34:08 +02:00
/**
* @ throws \Exception
*/
private function verifyConnector ( WatchList $watchList , ? Connector $connector ) : void
{
/** @var User $user */
$user = $this -> getUser ();
2024-09-07 01:14:50 +02:00
if ( null === $connector ) {
return ;
}
if ( ! $user -> getConnectors () -> contains ( $connector )) {
$this -> logger -> notice ( 'The Connector ({connector}) does not belong to the user.' , [
'username' => $user -> getUserIdentifier (),
'connector' => $connector -> getId (),
]);
throw new AccessDeniedHttpException ( 'You cannot create a Watchlist with a connector that does not belong to you' );
}
/** @var Domain $domain */
foreach ( $watchList -> getDomains () -> getIterator () as $domain ) {
if ( $domain -> getDeleted ()) {
$ldhName = $domain -> getLdhName ();
throw new BadRequestHttpException ( " To add a connector, no domain in this Watchlist must have already expired ( $ldhName ) " );
2024-08-19 16:34:08 +02:00
}
2024-09-07 01:14:50 +02:00
}
2024-08-19 16:34:08 +02:00
2024-09-07 01:14:50 +02:00
$connectorProviderClass = $connector -> getProvider () -> getConnectorProvider ();
/** @var AbstractProvider $connectorProvider */
2024-09-25 14:02:40 +02:00
$connectorProvider = $this -> locator -> get ( $connectorProviderClass );
2024-08-19 16:34:08 +02:00
2024-09-25 14:02:40 +02:00
$connectorProvider -> authenticate ( $connector -> getAuthData ());
2024-09-07 01:14:50 +02:00
$supported = $connectorProvider -> isSupported ( ... $watchList -> getDomains () -> toArray ());
2024-08-19 16:34:08 +02:00
2024-09-07 01:14:50 +02:00
if ( ! $supported ) {
$this -> logger -> notice ( 'The Connector ({connector}) does not support all TLDs in this Watchlist' , [
'username' => $user -> getUserIdentifier (),
'connector' => $connector -> getId (),
]);
throw new BadRequestHttpException ( 'This connector does not support all TLDs in this Watchlist' );
2024-08-19 16:34:08 +02:00
}
}
2024-08-15 03:04:31 +02:00
/**
* @ throws ORMException
2024-08-15 03:42:41 +02:00
* @ throws \Exception
2024-08-15 03:04:31 +02:00
*/
2024-08-14 23:23:32 +02:00
#[Route(
path : '/api/watchlists/{token}' ,
name : 'watchlist_update' ,
defaults : [
'_api_resource_class' => WatchList :: class ,
'_api_operation_name' => 'update' ,
],
2024-08-15 03:04:31 +02:00
methods : [ 'PUT' ]
2024-08-14 23:23:32 +02:00
)]
2024-08-15 03:04:31 +02:00
public function putWatchList ( WatchList $watchList ) : WatchList
2024-08-14 23:23:32 +02:00
{
2024-08-15 03:04:31 +02:00
/** @var User $user */
$user = $this -> getUser ();
2024-08-15 03:42:41 +02:00
$watchList -> setUser ( $user );
if ( $this -> getParameter ( 'limited_features' )) {
2024-08-15 04:06:35 +02:00
if ( $watchList -> getDomains () -> count () > ( int ) $this -> getParameter ( 'limit_max_watchlist_domains' )) {
2024-08-16 14:19:59 +02:00
$this -> logger -> notice ( 'User {username} tried to update a Watchlist. The maximum number of domains has been reached for this Watchlist' , [
2024-08-15 03:42:41 +02:00
'username' => $user -> getUserIdentifier (),
]);
throw new AccessDeniedHttpException ( 'You have exceeded the maximum number of domain names allowed in this Watchlist' );
}
$userWatchLists = $user -> getWatchLists ();
/** @var Domain[] $trackedDomains */
2024-08-15 03:50:29 +02:00
$trackedDomains = $userWatchLists
2024-08-15 04:06:35 +02:00
-> filter ( fn ( WatchList $wl ) => $wl -> getToken () !== $watchList -> getToken ())
2024-08-15 03:50:29 +02:00
-> reduce ( fn ( array $acc , WatchList $wl ) => [ ... $acc , ... $wl -> getDomains () -> toArray ()], []);
2024-08-15 03:42:41 +02:00
/** @var Domain $domain */
foreach ( $watchList -> getDomains () -> getIterator () as $domain ) {
if ( in_array ( $domain , $trackedDomains )) {
2024-08-16 14:19:59 +02:00
$ldhName = $domain -> getLdhName ();
$this -> logger -> notice ( 'User {username} tried to update a watchlist with domain name {ldhName}. It is forbidden to register the same domain name twice with limited mode' , [
2024-08-15 03:42:41 +02:00
'username' => $user -> getUserIdentifier (),
2024-08-16 14:19:59 +02:00
'ldhName' => $ldhName ,
2024-08-15 03:42:41 +02:00
]);
2024-08-15 03:04:31 +02:00
2024-08-16 14:19:59 +02:00
throw new AccessDeniedHttpException ( " It is forbidden to register the same domain name twice in your watchlists with limited mode ( $ldhName ) " );
2024-08-15 03:42:41 +02:00
}
}
2024-08-18 18:25:11 +02:00
if ( null !== $watchList -> getWebhookDsn () && count ( $watchList -> getWebhookDsn ()) > ( int ) $this -> getParameter ( 'limit_max_watchlist_webhooks' )) {
$this -> logger -> notice ( 'User {username} tried to update a Watchlist. The maximum number of webhooks has been reached.' , [
'username' => $user -> getUserIdentifier (),
]);
throw new AccessDeniedHttpException ( 'You have exceeded the maximum number of webhooks allowed in this Watchlist' );
}
2024-08-15 03:42:41 +02:00
}
2024-08-14 23:23:32 +02:00
2024-08-28 17:35:32 +02:00
$this -> chatNotificationService -> sendChatNotification ( $watchList , new TestChatNotification ());
2024-08-19 16:34:08 +02:00
$this -> verifyConnector ( $watchList , $watchList -> getConnector ());
2024-08-18 18:30:01 +02:00
2024-08-14 23:23:32 +02:00
$this -> logger -> info ( 'User {username} updates a Watchlist ({token}).' , [
2024-08-04 14:45:27 +02:00
'username' => $user -> getUserIdentifier (),
2024-08-04 22:00:20 +02:00
'token' => $watchList -> getToken (),
2024-08-04 14:45:27 +02:00
]);
2024-08-15 03:04:31 +02:00
$this -> em -> remove ( $this -> em -> getReference ( WatchList :: class , $watchList -> getToken ()));
$this -> em -> flush ();
2024-07-18 13:40:49 +02:00
$this -> em -> persist ( $watchList );
$this -> em -> flush ();
return $watchList ;
}
2024-08-01 14:37:23 +02:00
/**
* @ throws ParseException
* @ throws EofException
* @ throws InvalidDataException
2024-08-02 23:24:52 +02:00
* @ throws \Exception
2024-08-01 14:37:23 +02:00
*/
#[Route(
path : '/api/watchlists/{token}/calendar' ,
name : 'watchlist_calendar' ,
defaults : [
'_api_resource_class' => WatchList :: class ,
'_api_operation_name' => 'calendar' ,
]
)]
public function getWatchlistCalendar ( string $token ) : Response
{
2024-08-02 01:06:49 +02:00
/** @var WatchList $watchList */
2024-08-02 23:24:52 +02:00
$watchList = $this -> watchListRepository -> findOneBy ([ 'token' => $token ]);
2024-08-01 14:37:23 +02:00
$calendar = new Calendar ();
2024-08-03 17:55:39 +02:00
/** @var Domain $domain */
2024-08-01 14:37:23 +02:00
foreach ( $watchList -> getDomains () -> getIterator () as $domain ) {
$attendees = [];
/** @var DomainEntity $entity */
foreach ( $domain -> getDomainEntities () -> toArray () as $entity ) {
$vCard = Reader :: readJson ( $entity -> getEntity () -> getJCard ());
2024-08-03 00:06:38 +02:00
if ( isset ( $vCard -> EMAIL ) && isset ( $vCard -> FN )) {
$email = ( string ) $vCard -> EMAIL ;
if ( ! filter_var ( $email , FILTER_VALIDATE_EMAIL )) {
continue ;
}
$attendees [] = ( new Attendee ( new EmailAddress ( $email ))) -> setDisplayName (( string ) $vCard -> FN );
2024-08-02 23:24:52 +02:00
}
2024-08-01 14:37:23 +02:00
}
/** @var DomainEvent $event */
2024-09-02 22:20:03 +02:00
foreach ( $domain -> getEvents () -> filter ( fn ( DomainEvent $e ) => $e -> getDate () -> diff ( new \DateTimeImmutable ( 'now' )) -> y <= 10 ) -> getIterator () as $event ) {
2024-08-01 14:37:23 +02:00
$calendar -> addEvent (( new Event ())
2024-08-03 17:55:39 +02:00
-> setLastModified ( new Timestamp ( $domain -> getUpdatedAt ()))
-> setStatus ( EventStatus :: CONFIRMED ())
2024-08-02 23:24:52 +02:00
-> setSummary ( $domain -> getLdhName () . ' (' . $event -> getAction () . ')' )
2024-08-01 14:37:23 +02:00
-> addCategory ( new Category ( $event -> getAction ()))
-> setAttendees ( $attendees )
-> setOccurrence ( new SingleDay ( new Date ( $event -> getDate ())))
);
}
}
2024-08-03 18:05:05 +02:00
$calendarResponse = ( new CalendarFactory ()) -> createCalendar ( $calendar );
$calendarName = $watchList -> getName ();
if ( null !== $calendarName ) {
$calendarResponse -> withProperty ( new Property ( 'X-WR-CALNAME' , new TextValue ( $calendarName )));
}
return new Response ( $calendarResponse , Response :: HTTP_OK , [
2024-08-02 23:24:52 +02:00
'Content-Type' => 'text/calendar; charset=utf-8' ,
2024-08-01 14:37:23 +02:00
]);
}
2024-09-09 11:31:33 +02:00
/**
* @ throws \Exception
*/
2024-08-01 14:37:23 +02:00
#[Route(
2024-09-09 11:31:33 +02:00
path : '/api/tracked' ,
name : 'watchlist_get_tracked_domains' ,
2024-08-01 14:37:23 +02:00
defaults : [
'_api_resource_class' => WatchList :: class ,
2024-09-09 11:31:33 +02:00
'_api_operation_name' => 'get_tracked_domains' ,
]
2024-08-01 14:37:23 +02:00
)]
2024-09-09 11:31:33 +02:00
public function getTrackedDomains () : array
2024-08-01 14:37:23 +02:00
{
/** @var User $user */
$user = $this -> getUser ();
2024-08-02 23:24:52 +02:00
2024-09-09 11:31:33 +02:00
$domains = [];
/** @var WatchList $watchList */
foreach ( $user -> getWatchLists () -> getIterator () as $watchList ) {
/** @var Domain $domain */
foreach ( $watchList -> getDomains () -> getIterator () as $domain ) {
/** @var DomainEvent|null $exp */
$exp = $domain -> getEvents () -> findFirst ( fn ( int $key , DomainEvent $e ) => ! $e -> getDeleted () && 'expiration' === $e -> getAction ());
if ( ! $domain -> getDeleted ()
&& null !== $exp && $exp -> getDate () > new \DateTimeImmutable ()
&& count ( array_filter ( $domain -> getEvents () -> toArray (), fn ( DomainEvent $e ) => ! $e -> getDeleted () && 'expiration' === $e -> getAction ())) > 0
&& ! in_array ( $domain , $domains )) {
$domains [] = $domain ;
}
}
}
usort ( $domains , function ( Domain $d1 , Domain $d2 ) {
$IMPORTANT_STATUS = [ 'pending delete' , 'redemption period' , 'auto renew period' ];
/** @var \DateTimeImmutable $exp1 */
$exp1 = $d1 -> getEvents () -> findFirst ( fn ( int $key , DomainEvent $e ) => ! $e -> getDeleted () && 'expiration' === $e -> getAction ()) -> getDate ();
/** @var \DateTimeImmutable $exp2 */
$exp2 = $d2 -> getEvents () -> findFirst ( fn ( int $key , DomainEvent $e ) => ! $e -> getDeleted () && 'expiration' === $e -> getAction ()) -> getDate ();
$impStatus1 = count ( array_intersect ( $IMPORTANT_STATUS , $d1 -> getStatus ())) > 0 ;
$impStatus2 = count ( array_intersect ( $IMPORTANT_STATUS , $d2 -> getStatus ())) > 0 ;
return $impStatus1 && ! $impStatus2 ? - 1 : (
! $impStatus1 && $impStatus2 ? 2 :
$exp1 <=> $exp2
);
});
return $domains ;
2024-08-01 14:37:23 +02:00
}
2024-08-02 23:24:52 +02:00
}