Merge branch 'feat/send-webhook'

This commit is contained in:
Maël Gangloff
2024-08-17 22:37:09 +02:00
24 changed files with 1197 additions and 144 deletions

View File

@@ -8,7 +8,7 @@ import {Connector} from "../../../utils/api/connectors";
export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}: {
watchlist: Watchlist,
onUpdateWatchlist: (values: { domains: string[], emailTriggers: string[], token: string }) => Promise<void>,
onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise<void>,
connectors: (Connector & { id: string })[]
}) {
@@ -35,7 +35,8 @@ export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}
{name: 'name', value: watchlist.name},
{name: 'connector', value: watchlist.connector?.id},
{name: 'domains', value: watchlist.domains.map(d => d.ldhName)},
{name: 'emailTriggers', value: watchlist.triggers?.map(t => t.event)},
{name: 'triggers', value: [...new Set(watchlist.triggers?.map(t => t.event))]},
{name: 'dsn', value: watchlist.dsn}
])
}}/>
</Typography.Link>

View File

@@ -1,4 +1,4 @@
import {Button, Form, FormInstance, Input, Select, SelectProps, Space, Tag} from "antd";
import {Button, Form, FormInstance, Input, Select, SelectProps, Space, Tag, Typography} from "antd";
import {t} from "ttag";
import {ApiOutlined, MinusCircleOutlined, PlusOutlined} from "@ant-design/icons";
import React from "react";
@@ -28,7 +28,7 @@ const formItemLayoutWithOutLabel = {
export function WatchlistForm({form, connectors, onFinish, isCreation}: {
form: FormInstance,
connectors: (Connector & { id: string })[]
onFinish: (values: { domains: string[], emailTriggers: string[], token: string }) => void
onFinish: (values: { domains: string[], triggers: string[], token: string }) => void
isCreation: boolean
}) {
const domainEventTranslated = domainEvent()
@@ -56,7 +56,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
{...formItemLayoutWithOutLabel}
form={form}
onFinish={onFinish}
initialValues={{emailTriggers: ['last changed', 'transfer', 'expiration', 'deletion']}}
initialValues={{triggers: ['last changed', 'transfer', 'expiration', 'deletion']}}
>
<Form.Item name='token' hidden>
@@ -140,7 +140,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
)}
</Form.List>
<Form.Item label={t`Tracked events`}
name='emailTriggers'
name='triggers'
rules={[{required: true, message: t`At least one trigger`, type: 'array'}]}
labelCol={{
xs: {span: 24},
@@ -186,7 +186,58 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
}))}
/>
</Form.Item>
<Form.Item>
<Form.List
name="dsn">
{(fields, {add, remove}, {errors}) => (
<>
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? t`DSN` : ''}
required={true}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[{
required: true,
message: t`Required`
}, {
pattern: /:\/\//,
message: t`This DSN does not appear to be valid`
}]}
noStyle
>
<Input placeholder={t`slack://TOKEN@default?channel=CHANNEL`} style={{width: '60%'}} autoComplete='off'/>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
) : null}
</Form.Item>
))}
<Form.Item help={
<Typography.Link href='https://symfony.com/doc/current/notifier.html#chat-channel'>
{t`Check out this link to the Symfony documentation to help you build the DSN`}
</Typography.Link>}
>
<Button
type="dashed"
onClick={() => add()}
style={{width: '60%'}}
icon={<PlusOutlined/>}
>
{t`Add a Webhook`}
</Button>
<Form.ErrorList errors={errors}/>
</Form.Item>
</>
)}
</Form.List>
<Form.Item style={{marginTop: '10px'}}>
<Space>
<Button type="primary" htmlType="submit">
{isCreation ? t`Create` : t`Update`}

View File

@@ -14,7 +14,7 @@ import {ViewDiagramWatchlistButton} from "./diagram/ViewDiagramWatchlistButton";
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
watchlists: Watchlist[],
onDelete: () => void,
onUpdateWatchlist: (values: { domains: string[], emailTriggers: string[], token: string }) => Promise<void>,
onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise<void>,
connectors: (Connector & { id: string })[]
}) {
const sm = useBreakpoint('sm')

View File

@@ -14,6 +14,7 @@ export type Watchlist = {
token: string,
domains: { ldhName: string }[],
triggers?: { event: EventAction, action: string }[],
dsn?: string[]
connector?: {
id: string
provider: string
@@ -22,6 +23,33 @@ export type Watchlist = {
createdAt: string
}
type FormValuesType = {
name?: string
domains: string[],
triggers: string[]
connector?: string,
dsn?: string[]
}
const getRequestDataFromForm = (values: FormValuesType) => {
const domainsURI = values.domains.map(d => '/api/domains/' + d.toLowerCase())
let triggers = values.triggers.map(t => ({event: t, action: 'email'}))
if (values.dsn !== undefined) {
triggers = [...triggers, ...values.triggers.map(t => ({
event: t,
action: 'chat'
}))]
}
return {
name: values.name,
domains: domainsURI,
triggers,
connector: values.connector !== undefined ? ('/api/connectors/' + values.connector) : undefined,
dsn: values.dsn
}
}
export default function WatchlistPage() {
const [form] = Form.useForm()
@@ -29,19 +57,9 @@ export default function WatchlistPage() {
const [watchlists, setWatchlists] = useState<Watchlist[] | null>()
const [connectors, setConnectors] = useState<(Connector & { id: string })[] | null>()
const onCreateWatchlist = (values: {
name?: string
domains: string[],
emailTriggers: string[]
connector?: string
}) => {
const domainsURI = values.domains.map(d => '/api/domains/' + d.toLowerCase())
postWatchlist({
name: values.name,
domains: domainsURI,
triggers: values.emailTriggers.map(t => ({event: t, action: 'email'})),
connector: values.connector !== undefined ? ('/api/connectors/' + values.connector) : undefined
}).then((w) => {
const onCreateWatchlist = (values: FormValuesType) => {
postWatchlist(getRequestDataFromForm(values)).then((w) => {
form.resetFields()
refreshWatchlists()
messageApi.success(t`Watchlist created !`)
@@ -50,28 +68,16 @@ export default function WatchlistPage() {
})
}
const onUpdateWatchlist = async (values: {
token: string
name?: string
domains: string[],
emailTriggers: string[]
connector?: string
}) => {
const domainsURI = values.domains.map(d => '/api/domains/' + d.toLowerCase())
return putWatchlist({
const onUpdateWatchlist = async (values: FormValuesType & { token: string }) => putWatchlist({
token: values.token,
name: values.name,
domains: domainsURI,
triggers: values.emailTriggers.map(t => ({event: t, action: 'email'})),
connector: values.connector !== undefined ? ('/api/connectors/' + values.connector) : undefined
}).then((w) => {
refreshWatchlists()
messageApi.success(t`Watchlist updated !`)
}).catch((e: AxiosError) => {
throw showErrorAPI(e, messageApi)
})
}
...getRequestDataFromForm(values)
}
).then((w) => {
refreshWatchlists()
messageApi.success(t`Watchlist updated !`)
}).catch((e: AxiosError) => {
throw showErrorAPI(e, messageApi)
})
const refreshWatchlists = () => getWatchlists().then(w => {
setWatchlists(w['hydra:member'])

View File

@@ -69,6 +69,7 @@ export interface WatchlistRequest {
domains: string[],
triggers: { event: EventAction, action: TriggerAction }[],
connector?: string
dsn?: string[]
}
export interface Watchlist {
@@ -78,6 +79,7 @@ export interface Watchlist {
triggers: { event: EventAction, action: TriggerAction }[],
connector?: string
createdAt: string
dsn?: string[]
}
export interface InstanceConfig {

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

@@ -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,10 @@ 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\Exception\TransportExceptionInterface;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\SerializerInterface;
@@ -45,6 +51,29 @@ class WatchListController extends AbstractController
) {
}
/**
* @throws TransportExceptionInterface
*/
private function verifyWebhookDSN(WatchList $watchList): void
{
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());
}
}
}
/**
* @throws \Exception
*/
@@ -65,6 +94,8 @@ class WatchListController extends AbstractController
$user = $this->getUser();
$watchList->setUser($user);
$this->verifyWebhookDSN($watchList);
/*
* 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 +182,8 @@ class WatchListController extends AbstractController
$user = $this->getUser();
$watchList->setUser($user);
$this->verifyWebhookDSN($watchList);
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,51 @@ 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\Component\Notifier\Transport\Dsn;
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 +78,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 +103,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 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,47 @@
<?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
{
$ldhName = $this->domain->getLdhName();
$this->subject("Error: Domain Order $ldhName")
->content("Domain name $ldhName tried to be purchased. The attempt failed.")
->importance(Notification::IMPORTANCE_HIGH);
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,53 @@
<?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
{
$ldhName = $this->domain->getLdhName();
$this
->subject("Success: Domain Ordered $ldhName!")
->content("Domain name $ldhName has just been purchased. The API provider did not return an error.")
->importance(Notification::IMPORTANCE_HIGH);
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,47 @@
<?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
{
$ldhName = $this->domain->getLdhName();
$this->subject("Error: Domain Update $ldhName")
->content("Domain name $ldhName tried to be updated. The attempt failed.")
->importance(Notification::IMPORTANCE_MEDIUM);
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,50 @@
<?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
{
$ldhName = $this->domainEvent->getDomain()->getLdhName();
$action = $this->domainEvent->getAction();
$this->subject("Domain changed $ldhName ($action)")
->content("Domain name $ldhName information has been updated ($action).")
->importance(Notification::IMPORTANCE_HIGH);
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,21 @@
<?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')
->content('This is a test message. If you can read me, this Webhook is configured correctly')
->importance(Notification::IMPORTANCE_LOW);
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

@@ -23,6 +23,7 @@ msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:156
#: assets/components/tracking/connector/ConnectorForm.tsx:165
#: assets/components/tracking/watchlist/WatchlistForm.tsx:109
#: assets/components/tracking/watchlist/WatchlistForm.tsx:205
msgid "Required"
msgstr ""
@@ -203,12 +204,12 @@ msgid ""
msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:176
#: assets/components/tracking/watchlist/WatchlistForm.tsx:192
#: assets/components/tracking/watchlist/WatchlistForm.tsx:243
msgid "Create"
msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:179
#: assets/components/tracking/watchlist/WatchlistForm.tsx:195
#: assets/components/tracking/watchlist/WatchlistForm.tsx:246
msgid "Reset"
msgstr ""
@@ -284,7 +285,27 @@ msgid ""
"that may be available soon."
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:192
#: assets/components/tracking/watchlist/WatchlistForm.tsx:196
msgid "DSN"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:208
msgid "This DSN does not appear to be valid"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:212
msgid "slack://TOKEN@default?channel=CHANNEL"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:224
msgid "Check out this link to the Symfony documentation to help you build the DSN"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:233
msgid "Add a Webhook"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:243
msgid "Update"
msgstr ""
@@ -292,11 +313,11 @@ msgstr ""
msgid "Edit the Watchlist"
msgstr ""
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:43
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:44
msgid "Update a Watchlist"
msgstr ""
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:53
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:54
msgid "Cancel"
msgstr ""
@@ -526,15 +547,15 @@ msgstr ""
msgid "Create a Connector"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:47
#: assets/pages/tracking/WatchlistPage.tsx:65
msgid "Watchlist created !"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:70
#: assets/pages/tracking/WatchlistPage.tsx:77
msgid "Watchlist updated !"
msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:96
#: assets/pages/tracking/WatchlistPage.tsx:102
msgid "Create a Watchlist"
msgstr ""

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==