feat: start webhook support

This commit is contained in:
Maël Gangloff 2024-08-16 23:23:51 +02:00
parent 1c1821838f
commit 8667644da5
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
19 changed files with 1050 additions and 94 deletions

View File

@ -41,16 +41,20 @@
"symfony/asset": "7.1.*",
"symfony/asset-mapper": "7.1.*",
"symfony/console": "7.1.*",
"symfony/discord-notifier": "7.1.*",
"symfony/doctrine-messenger": "7.1.*",
"symfony/dotenv": "7.1.*",
"symfony/expression-language": "7.1.*",
"symfony/flex": "^2",
"symfony/form": "7.1.*",
"symfony/framework-bundle": "7.1.*",
"symfony/google-chat-notifier": "7.1.*",
"symfony/http-client": "7.1.*",
"symfony/intl": "7.1.*",
"symfony/lock": "7.1.*",
"symfony/mailer": "7.1.*",
"symfony/mattermost-notifier": "7.1.*",
"symfony/microsoft-teams-notifier": "7.1.*",
"symfony/mime": "7.1.*",
"symfony/monolog-bundle": "^3.0",
"symfony/notifier": "7.1.*",
@ -58,12 +62,15 @@
"symfony/property-access": "7.1.*",
"symfony/property-info": "7.1.*",
"symfony/rate-limiter": "7.1.*",
"symfony/rocket-chat-notifier": "7.1.*",
"symfony/runtime": "7.1.*",
"symfony/scheduler": "7.1.*",
"symfony/security-bundle": "7.1.*",
"symfony/serializer": "7.1.*",
"symfony/slack-notifier": "7.1.*",
"symfony/stimulus-bundle": "^2.18",
"symfony/string": "7.1.*",
"symfony/telegram-notifier": "7.1.*",
"symfony/translation": "7.1.*",
"symfony/twig-bundle": "7.1.*",
"symfony/uid": "7.1.*",
@ -72,6 +79,7 @@
"symfony/web-link": "7.1.*",
"symfony/webpack-encore-bundle": "^2.1",
"symfony/yaml": "7.1.*",
"symfony/zulip-notifier": "7.1.*",
"symfonycasts/verify-email-bundle": "*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"

547
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bab584811b8175e404608e6738549f52",
"content-hash": "f64fa606b60efd34dccdee3abcdad8b2",
"packages": [
{
"name": "api-platform/core",
@ -4325,6 +4325,74 @@
],
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/discord-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/discord-notifier.git",
"reference": "f3d8368ca5ff80c1268a851f925e1f0c07997a8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/discord-notifier/zipball/f3d8368ca5ff80c1268a851f925e1f0c07997a8e",
"reference": "f3d8368ca5ff80c1268a851f925e1f0c07997a8e",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0",
"symfony/polyfill-mbstring": "^1.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\Discord\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Discord Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"discord",
"notifier"
],
"support": {
"source": "https://github.com/symfony/discord-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/doctrine-bridge",
"version": "v7.1.2",
@ -5313,6 +5381,75 @@
],
"time": "2024-06-28T08:00:31+00:00"
},
{
"name": "symfony/google-chat-notifier",
"version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/google-chat-notifier.git",
"reference": "1e92b6c89b2182ba26554861dc261c530c98000f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/google-chat-notifier/zipball/1e92b6c89b2182ba26554861dc261c530c98000f",
"reference": "1e92b6c89b2182ba26554861dc261c530c98000f",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\GoogleChat\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Google Chat Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"Google-Chat",
"chat",
"google",
"notifier"
],
"support": {
"source": "https://github.com/symfony/google-chat-notifier/tree/v7.1.2"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-06-25T19:55:06+00:00"
},
{
"name": "symfony/http-client",
"version": "v7.1.2",
@ -5920,6 +6057,73 @@
],
"time": "2024-06-28T08:00:31+00:00"
},
{
"name": "symfony/mattermost-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/mattermost-notifier.git",
"reference": "c5ff6774682ab3504a77bbe01f8c1275b4bf48e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mattermost-notifier/zipball/c5ff6774682ab3504a77bbe01f8c1275b4bf48e9",
"reference": "c5ff6774682ab3504a77bbe01f8c1275b4bf48e9",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\Mattermost\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuele Panzeri",
"email": "thepanz@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Mattermost Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"Mattermost",
"notifier"
],
"support": {
"source": "https://github.com/symfony/mattermost-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/messenger",
"version": "v7.1.2",
@ -6006,6 +6210,78 @@
],
"time": "2024-06-28T08:00:31+00:00"
},
{
"name": "symfony/microsoft-teams-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/microsoft-teams-notifier.git",
"reference": "546b0368928b5849d08728b7daf5d22a07a052b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/microsoft-teams-notifier/zipball/546b0368928b5849d08728b7daf5d22a07a052b3",
"reference": "546b0368928b5849d08728b7daf5d22a07a052b3",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\MicrosoftTeams\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Edouard Lescot",
"email": "edouard.lescot@gmail.com"
},
{
"name": "Oskar Stark",
"email": "oskarstark@googlemail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Microsoft Teams Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"chat",
"microsoft-teams",
"notifier"
],
"support": {
"source": "https://github.com/symfony/microsoft-teams-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/mime",
"version": "v7.1.2",
@ -7319,6 +7595,73 @@
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/rocket-chat-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/rocket-chat-notifier.git",
"reference": "b17bff59107b51753e3e347c5194dc304019daf7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/rocket-chat-notifier/zipball/b17bff59107b51753e3e347c5194dc304019daf7",
"reference": "b17bff59107b51753e3e347c5194dc304019daf7",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\RocketChat\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jeroen Spee",
"homepage": "https://github.com/Jeroeny"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony RocketChat Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"notifier",
"rocketchat"
],
"support": {
"source": "https://github.com/symfony/rocket-chat-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/routing",
"version": "v7.1.1",
@ -8087,6 +8430,73 @@
],
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/slack-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/slack-notifier.git",
"reference": "452a17e3935192e6a9a5b16f0443911d67e456af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/slack-notifier/zipball/452a17e3935192e6a9a5b16f0443911d67e456af",
"reference": "452a17e3935192e6a9a5b16f0443911d67e456af",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\Slack\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Slack Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"notifier",
"slack"
],
"support": {
"source": "https://github.com/symfony/slack-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/stimulus-bundle",
"version": "v2.18.1",
@ -8305,6 +8715,74 @@
],
"time": "2024-06-28T09:27:18+00:00"
},
{
"name": "symfony/telegram-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/telegram-notifier.git",
"reference": "521e77470d5b07306c1001c2d1d1bc88474a8035"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/telegram-notifier/zipball/521e77470d5b07306c1001c2d1d1bc88474a8035",
"reference": "521e77470d5b07306c1001c2d1d1bc88474a8035",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/mime": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\Telegram\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Telegram Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"notifier",
"telegram"
],
"support": {
"source": "https://github.com/symfony/telegram-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/translation",
"version": "v7.1.1",
@ -9404,6 +9882,73 @@
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/zulip-notifier",
"version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/zulip-notifier.git",
"reference": "48b3e1ac791d8eac7ee268108865b36de3be5ed2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/zulip-notifier/zipball/48b3e1ac791d8eac7ee268108865b36de3be5ed2",
"reference": "48b3e1ac791d8eac7ee268108865b36de3be5ed2",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/http-client": "^6.4|^7.0",
"symfony/notifier": "^6.4|^7.0"
},
"type": "symfony-notifier-bridge",
"autoload": {
"psr-4": {
"Symfony\\Component\\Notifier\\Bridge\\Zulip\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mohammad Emran Hasan",
"email": "phpfour@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Zulip Notifier Bridge",
"homepage": "https://symfony.com",
"keywords": [
"notifier",
"zulip"
],
"support": {
"source": "https://github.com/symfony/zulip-notifier/tree/v7.1.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfonycasts/verify-email-bundle",
"version": "v1.17.0",

View File

@ -1,6 +1,14 @@
framework:
notifier:
chatter_transports:
zulip: '%env(ZULIP_DSN)%'
telegram: '%env(TELEGRAM_DSN)%'
slack: '%env(SLACK_DSN)%'
rocketchat: '%env(ROCKETCHAT_DSN)%'
microsoftteams: '%env(MICROSOFT_TEAMS_DSN)%'
mattermost: '%env(MATTERMOST_DSN)%'
googlechat: '%env(GOOGLE_CHAT_DSN)%'
discord: '%env(DISCORD_DSN)%'
texter_transports:
channel_policy:
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo

View File

@ -14,6 +14,8 @@ parameters:
limit_max_watchlist_domains: '%env(int:LIMIT_MAX_WATCHLIST_DOMAINS)%'
services:
Symfony\Component\Messenger\Transport\TransportFactoryInterface: '@messenger.transport_factory'
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240816185909 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE watch_list ADD webhook_dsn TEXT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN watch_list.webhook_dsn IS \'(DC2Type:simple_array)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE watch_list DROP webhook_dsn');
}
}

View File

@ -19,6 +19,10 @@
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.24.7",
"@fontsource/noto-color-emoji": "^5.0.27",
"@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.1.1 || ^8.0",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@symfony/webpack-encore": "^4.0.0",
"@types/axios": "^0.14.0",
"@types/dagre": "^0.7.52",

View File

@ -5,4 +5,5 @@ namespace App\Config;
enum TriggerAction: string
{
case SendEmail = 'email';
case SendChat = 'chat';
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Config;
use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory;
use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory;
use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory;
use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory;
use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory;
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory;
use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory;
enum WebhookScheme: string
{
case DISCORD = 'discord';
case GOOGLE_CHAT = 'googlechat';
case MATTERMOST = 'mattermost';
case MICROSOFT_TEAMS = 'microsoftteams';
case ROCKET_CHAT = 'rocketchat';
case SLACK = 'slack';
case TELEGRAM = 'telegram';
case ZULIP = 'zulip';
public function getChatTransportFactory(): string
{
return match ($this) {
WebhookScheme::DISCORD => DiscordTransportFactory::class,
WebhookScheme::GOOGLE_CHAT => GoogleChatTransportFactory::class,
WebhookScheme::MATTERMOST => MattermostTransportFactory::class,
WebhookScheme::MICROSOFT_TEAMS => MicrosoftTeamsTransportFactory::class,
WebhookScheme::ROCKET_CHAT => RocketChatTransportFactory::class,
WebhookScheme::SLACK => SlackTransportFactory::class,
WebhookScheme::TELEGRAM => TelegramTransportFactory::class,
WebhookScheme::ZULIP => ZulipTransportFactory::class
};
}
}

View File

@ -2,11 +2,13 @@
namespace App\Controller;
use App\Config\WebhookScheme;
use App\Entity\Domain;
use App\Entity\DomainEntity;
use App\Entity\DomainEvent;
use App\Entity\User;
use App\Entity\WatchList;
use App\Notifier\TestChatNotification;
use App\Repository\WatchListRepository;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
@ -32,6 +34,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\SerializerInterface;
@ -65,6 +70,23 @@ class WatchListController extends AbstractController
$user = $this->getUser();
$watchList->setUser($user);
if (null !== $watchList->getWebhookDsn()) {
foreach ($watchList->getWebhookDsn() as $dsnString) {
$dsn = new Dsn($dsnString);
$scheme = $dsn->getScheme();
$webhookScheme = WebhookScheme::tryFrom($scheme);
if (null === $webhookScheme) {
throw new BadRequestHttpException("The DSN scheme ($scheme) is not supported");
}
$transportFactoryClass = $webhookScheme->getChatTransportFactory();
/** @var AbstractTransportFactory $transportFactory */
$transportFactory = new $transportFactoryClass();
$transportFactory->create($dsn)->send((new TestChatNotification())->asChatMessage());
}
}
/*
* 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.
@ -151,6 +173,16 @@ class WatchListController extends AbstractController
$user = $this->getUser();
$watchList->setUser($user);
if (null !== $watchList->getWebhookDsn()) {
foreach ($watchList->getWebhookDsn() as $dsnString) {
$scheme = (new Dsn($dsnString))->getScheme();
if (null === WebhookScheme::tryFrom($scheme)) {
throw new BadRequestHttpException("The DSN scheme ($scheme) is not supported");
}
}
}
if ($this->getParameter('limited_features')) {
if ($watchList->getDomains()->count() > (int) $this->getParameter('limit_max_watchlist_domains')) {
$this->logger->notice('User {username} tried to update a Watchlist. The maximum number of domains has been reached for this Watchlist', [

View File

@ -12,6 +12,7 @@ use App\Controller\WatchListController;
use App\Repository\WatchListRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\SerializedName;
@ -118,6 +119,11 @@ class WatchList
#[Groups(['watchlist:list', 'watchlist:item'])]
private ?\DateTimeImmutable $createdAt = null;
#[SerializedName('dsn')]
#[ORM\Column(type: Types::SIMPLE_ARRAY, nullable: true)]
#[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])]
private ?array $webhookDsn = null;
public function __construct()
{
$this->token = Uuid::v4();
@ -237,4 +243,16 @@ class WatchList
return $this;
}
public function getWebhookDsn(): ?array
{
return $this->webhookDsn;
}
public function setWebhookDsn(?array $webhookDsn): static
{
$this->webhookDsn = $webhookDsn;
return $this;
}
}

View File

@ -4,43 +4,50 @@ namespace App\MessageHandler;
use App\Config\Connector\ConnectorInterface;
use App\Config\TriggerAction;
use App\Entity\Connector;
use App\Config\WebhookScheme;
use App\Entity\Domain;
use App\Entity\DomainEvent;
use App\Entity\User;
use App\Entity\WatchList;
use App\Entity\WatchListTrigger;
use App\Message\ProcessDomainTrigger;
use App\Notifier\DomainOrderErrorNotification;
use App\Notifier\DomainOrderNotification;
use App\Notifier\DomainUpdateNotification;
use App\Repository\DomainRepository;
use App\Repository\WatchListRepository;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Notifier\Recipient\Recipient;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[AsMessageHandler]
final readonly class ProcessDomainTriggerHandler
{
private Address $sender;
public function __construct(
private string $mailerSenderEmail,
private string $mailerSenderName,
private MailerInterface $mailer,
string $mailerSenderEmail,
string $mailerSenderName,
private WatchListRepository $watchListRepository,
private DomainRepository $domainRepository,
private KernelInterface $kernel,
private LoggerInterface $logger,
private HttpClientInterface $client
private HttpClientInterface $client,
private MailerInterface $mailer
) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
}
/**
* @throws TransportExceptionInterface
* @throws \Exception
* @throws ExceptionInterface
*/
public function __invoke(ProcessDomainTrigger $message): void
{
@ -70,12 +77,16 @@ final readonly class ProcessDomainTriggerHandler
$connectorProvider->orderDomain($domain, $this->kernel->isDebug());
$this->sendEmailDomainOrdered($domain, $connector, $watchList->getUser());
$email = (new DomainOrderNotification($this->sender, $domain, $connector))
->asEmailMessage(new Recipient($watchList->getUser()->getEmail()));
$this->mailer->send($email->getMessage());
} catch (\Throwable) {
$this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$this->sendEmailDomainOrderError($domain, $watchList->getUser());
$email = (new DomainOrderErrorNotification($this->sender, $domain))
->asEmailMessage(new Recipient($watchList->getUser()->getEmail()));
$this->mailer->send($email->getMessage());
}
}
@ -91,67 +102,29 @@ final readonly class ProcessDomainTriggerHandler
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$recipient = new Recipient($watchList->getUser()->getEmail());
$notification = new DomainUpdateNotification($this->sender, $event);
if (TriggerAction::SendEmail == $watchListTrigger->getAction()) {
$this->sendEmailDomainUpdated($event, $watchList->getUser());
$this->mailer->send($notification->asEmailMessage($recipient)->getMessage());
} elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) {
if (null !== $watchList->getWebhookDsn()) {
foreach ($watchList->getWebhookDsn() as $dsnString) {
$dsn = new \Symfony\Component\Notifier\Transport\Dsn($dsnString);
$scheme = $dsn->getScheme();
$webhookScheme = WebhookScheme::tryFrom($scheme);
if (null !== $webhookScheme) {
$transportFactoryClass = $webhookScheme->getChatTransportFactory();
/** @var AbstractTransportFactory $transportFactory */
$transportFactory = new $transportFactoryClass();
$transportFactory->create($dsn)->send($notification->asChatMessage());
}
}
}
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainOrdered(Domain $domain, Connector $connector, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->priority(Email::PRIORITY_HIGHEST)
->subject('A domain name has been ordered')
->htmlTemplate('emails/success/domain_ordered.html.twig')
->locale('en')
->context([
'domain' => $domain,
'provider' => $connector->getProvider()->value,
]);
$this->mailer->send($email);
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainOrderError(Domain $domain, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->subject('An error occurred while ordering a domain name')
->htmlTemplate('emails/errors/domain_order.html.twig')
->locale('en')
->context([
'domain' => $domain,
]);
$this->mailer->send($email);
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainUpdated(DomainEvent $domainEvent, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->priority(Email::PRIORITY_HIGHEST)
->subject('A domain name has been changed')
->htmlTemplate('emails/success/domain_updated.html.twig')
->locale('en')
->context([
'event' => $domainEvent,
]);
$this->mailer->send($email);
}
}

View File

@ -3,33 +3,36 @@
namespace App\MessageHandler;
use App\Entity\Domain;
use App\Entity\User;
use App\Entity\WatchList;
use App\Message\ProcessDomainTrigger;
use App\Message\ProcessWatchListTrigger;
use App\Notifier\DomainUpdateErrorNotification;
use App\Repository\WatchListRepository;
use App\Service\RDAPService;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Notifier\Recipient\Recipient;
#[AsMessageHandler]
final readonly class ProcessWatchListTriggerHandler
{
private Address $sender;
public function __construct(
private RDAPService $RDAPService,
private MailerInterface $mailer,
private string $mailerSenderEmail,
private string $mailerSenderName,
string $mailerSenderEmail,
string $mailerSenderName,
private MessageBusInterface $bus,
private WatchListRepository $watchListRepository,
private LoggerInterface $logger
) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName);
}
/**
@ -63,28 +66,12 @@ final readonly class ProcessWatchListTriggerHandler
'username' => $watchList->getUser()->getUserIdentifier(),
'error' => $e,
]);
$this->sendEmailDomainUpdateError($domain, $watchList->getUser());
$email = (new DomainUpdateErrorNotification($this->sender, $domain))
->asEmailMessage(new Recipient($watchList->getUser()->getEmail()));
$this->mailer->send($email->getMessage());
}
$this->bus->dispatch(new ProcessDomainTrigger($watchList->getToken(), $domain->getLdhName(), $updatedAt));
}
}
/**
* @throws TransportExceptionInterface
*/
private function sendEmailDomainUpdateError(Domain $domain, User $user): void
{
$email = (new TemplatedEmail())
->from(new Address($this->mailerSenderEmail, $this->mailerSenderName))
->to($user->getEmail())
->subject('An error occurred while updating a domain name')
->htmlTemplate('emails/errors/domain_update.html.twig')
->locale('en')
->context([
'domain' => $domain,
]);
$this->mailer->send($email);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Notifier;
use App\Entity\Domain;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mime\Address;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\ChatNotificationInterface;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class DomainOrderErrorNotification extends Notification implements ChatNotificationInterface, EmailNotificationInterface
{
public function __construct(
private readonly Address $sender,
private readonly Domain $domain
) {
parent::__construct();
}
public function asChatMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?ChatMessage
{
$this->subject('Error: Domain Order');
return ChatMessage::fromNotification($this);
}
public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): EmailMessage
{
return new EmailMessage((new TemplatedEmail())
->from($this->sender)
->to($recipient->getEmail())
->subject('An error occurred while ordering a domain name')
->htmlTemplate('emails/errors/domain_order.html.twig')
->locale('en')
->context([
'domain' => $this->domain,
]));
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Notifier;
use App\Entity\Connector;
use App\Entity\Domain;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\ChatNotificationInterface;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class DomainOrderNotification extends Notification implements ChatNotificationInterface, EmailNotificationInterface
{
public function __construct(
private readonly Address $sender,
private readonly Domain $domain,
private readonly Connector $connector
) {
parent::__construct();
}
public function asChatMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?ChatMessage
{
$this->subject('Domain Ordered');
return ChatMessage::fromNotification($this);
}
public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): EmailMessage
{
return new EmailMessage((new TemplatedEmail())
->from($this->sender)
->to($recipient->getEmail())
->priority(Email::PRIORITY_HIGHEST)
->subject('A domain name has been ordered')
->htmlTemplate('emails/success/domain_ordered.html.twig')
->locale('en')
->context([
'domain' => $this->domain,
'provider' => $this->connector->getProvider()->value,
]));
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Notifier;
use App\Entity\Domain;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mime\Address;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\ChatNotificationInterface;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class DomainUpdateErrorNotification extends Notification implements ChatNotificationInterface, EmailNotificationInterface
{
public function __construct(
private readonly Address $sender,
private readonly Domain $domain
) {
parent::__construct();
}
public function asChatMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?ChatMessage
{
$this->subject('Error: Domain Update');
return ChatMessage::fromNotification($this);
}
public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): EmailMessage
{
return new EmailMessage((new TemplatedEmail())
->from($this->sender)
->to($recipient->getEmail())
->subject('An error occurred while updating a domain name')
->htmlTemplate('emails/errors/domain_update.html.twig')
->locale('en')
->context([
'domain' => $this->domain,
]));
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Notifier;
use App\Entity\DomainEvent;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\ChatNotificationInterface;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class DomainUpdateNotification extends Notification implements ChatNotificationInterface, EmailNotificationInterface
{
public function __construct(
private readonly Address $sender,
private readonly DomainEvent $domainEvent
) {
parent::__construct();
}
public function asChatMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?ChatMessage
{
$this->subject('Domain Updated');
return ChatMessage::fromNotification($this);
}
public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): EmailMessage
{
return new EmailMessage((new TemplatedEmail())
->from($this->sender)
->to($recipient->getEmail())
->priority(Email::PRIORITY_HIGHEST)
->subject('A domain name has been changed')
->htmlTemplate('emails/success/domain_updated.html.twig')
->locale('en')
->context([
'event' => $this->domainEvent,
]));
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Notifier;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Notification\ChatNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class TestChatNotification extends Notification implements ChatNotificationInterface
{
public function asChatMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?ChatMessage
{
$this->subject('Test notification');
$this->content('This is a test message. If you can read me, this Webhook is configured correctly');
return ChatMessage::fromNotification($this);
}
}

View File

@ -153,6 +153,15 @@
"config/packages/debug.yaml"
]
},
"symfony/discord-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.2",
"ref": "b97655f9a2fb8fc04d9f4081e0b5599d897b7f6e"
}
},
"symfony/flex": {
"version": "2.4",
"recipe": {
@ -184,6 +193,15 @@
"src/Kernel.php"
]
},
"symfony/google-chat-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "5954a3403bf1cdc557e2b71d3854cc81ba7d37cb"
}
},
"symfony/lock": {
"version": "7.1",
"recipe": {
@ -217,6 +235,15 @@
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/mattermost-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.1",
"ref": "60df16a0ff39e942f6579e624d5630fc6b4e0cc1"
}
},
"symfony/messenger": {
"version": "7.1",
"recipe": {
@ -229,6 +256,15 @@
"config/packages/messenger.yaml"
]
},
"symfony/microsoft-teams-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "e4e1704f0b11573aaededc00640492c915de0bbe"
}
},
"symfony/monolog-bundle": {
"version": "3.10",
"recipe": {
@ -268,6 +304,15 @@
"tests/bootstrap.php"
]
},
"symfony/rocket-chat-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.1",
"ref": "5c91d24b503de5cc0d0eb880fc95afa1bef8b6f4"
}
},
"symfony/routing": {
"version": "7.1",
"recipe": {
@ -294,6 +339,15 @@
"config/routes/security.yaml"
]
},
"symfony/slack-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.2",
"ref": "8fb9603326990013efbe6d71c2dd78ada316808a"
}
},
"symfony/stimulus-bundle": {
"version": "2.18",
"recipe": {
@ -308,6 +362,15 @@
"assets/controllers/hello_controller.js"
]
},
"symfony/telegram-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.0",
"ref": "6cecb59a0e96c9e1cee469f2b82fa920101a68e8"
}
},
"symfony/translation": {
"version": "7.1",
"recipe": {
@ -387,6 +450,15 @@
"webpack.config.js"
]
},
"symfony/zulip-notifier": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.2",
"ref": "f420901c554baf7cde79a2a0bbf9b37ab1a650aa"
}
},
"symfonycasts/verify-email-bundle": {
"version": "v1.17.0"
},

View File

@ -1504,6 +1504,21 @@
resolved "https://registry.yarnpkg.com/@fontsource/noto-color-emoji/-/noto-color-emoji-5.0.27.tgz#61e40657bea980553bde8fd2d566104bd2859ad6"
integrity sha512-gsIMN5o8qoRLrA+XyDNKKnMNpTbwpNbINisJqirLOLXl83arOV2sHRnNOkVht2gzUcImUxEUBGektp56G3Vj9w==
"@hotwired/stimulus-webpack-helpers@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz#4cd74487adeca576c9865ac2b9fe5cb20cef16dd"
integrity sha512-wa/zupVG0eWxRYJjC1IiPBdt3Lruv0RqGN+/DTMmUWUyMAEB27KXmVY6a8YpUVTM7QwVuaLNGW4EqDgrS2upXQ==
"@hotwired/stimulus@^3.0.0":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608"
integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==
"@hotwired/turbo@^7.1.1 || ^8.0":
version "8.0.5"
resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-8.0.5.tgz#abae6dad018a891e4286e87fa0959217e3866d5a"
integrity sha512-TdZDA7fxVQ2ZycygvpnzjGPmFq4sO/E2QVg+2em/sJ3YTSsIWVEis8HmWlumz+c9DjWcUkcCuB+muF08TInpAQ==
"@jest/schemas@^29.6.3":
version "29.6.3"
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
@ -1670,6 +1685,20 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
"@symfony/stimulus-bridge@^3.2.0":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.2.tgz#afc1918f82d78cb2b6e299285c54094aa7f53696"
integrity sha512-kIaUEGPXW7g14zsHkIvQWw8cmfCdXSqsEQx18fuHPBb+R0h8nYPyY+e9uVtTuHlE2wHwAjrJoc6YBBK4a7CpKA==
dependencies:
"@hotwired/stimulus-webpack-helpers" "^1.0.1"
"@types/webpack-env" "^1.16.4"
acorn "^8.0.5"
loader-utils "^2.0.0"
schema-utils "^3.0.0"
"@symfony/ux-turbo@file:vendor/symfony/ux-turbo/assets":
version "0.1.0"
"@symfony/webpack-encore@^4.0.0":
version "4.6.1"
resolved "https://registry.yarnpkg.com/@symfony/webpack-encore/-/webpack-encore-4.6.1.tgz#a3ced0baf1b02feb6d1a564906aff0e479b07259"
@ -1993,6 +2022,11 @@
dependencies:
"@types/node" "*"
"@types/webpack-env@^1.16.4":
version "1.18.5"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.5.tgz#eccda0b04fe024bed505881e2e532f9c119169bf"
integrity sha512-wz7kjjRRj8/Lty4B+Kr0LN6Ypc/3SymeCCGSbaXp2leH0ZVg/PriNiOwNj4bD4uphI7A8NXS4b6Gl373sfO5mA==
"@types/ws@^8.5.5":
version "8.5.11"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.11.tgz#90ad17b3df7719ce3e6bc32f83ff954d38656508"
@ -2202,7 +2236,7 @@ acorn-import-attributes@^1.9.5:
resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
acorn@^8.7.1, acorn@^8.8.2:
acorn@^8.0.5, acorn@^8.7.1, acorn@^8.8.2:
version "8.12.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
@ -6020,7 +6054,7 @@ scheduler@^0.23.2:
dependencies:
loose-envify "^1.1.0"
schema-utils@^3.1.1, schema-utils@^3.2.0:
schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==