From d7d208d99b2d3119a5766d1438f1ebb0c5fdfc7e Mon Sep 17 00:00:00 2001 From: Arthur de Moulins Date: Wed, 12 Feb 2025 17:49:09 +0100 Subject: [PATCH] add notify admin to broadcast notifications --- databox/api/config/routes/alchemy_notify.yaml | 5 ++ .../Controller/Admin/DashboardController.php | 4 ++ expose/api/config/routes/alchemy_notify.yaml | 5 ++ .../notify-bundle/src/AlchemyNotifyBundle.php | 4 ++ .../Command/BroadcastNotificationCommand.php | 45 ++++++++++++ .../src/Controller/NotificationController.php | 68 +++++++++++++++++++ lib/php/notify-bundle/src/Form/NotifyForm.php | 43 ++++++++++++ .../notify-bundle/src/Model/Notification.php | 14 ++++ .../src/Notification/MockNotifier.php | 9 +++ .../src/Notification/NotifierInterface.php | 5 ++ .../src/Notification/SymfonyNotifier.php | 7 +- .../notify-bundle/src/Service/NovuClient.php | 21 ++++++ .../templates/admin/index.html.twig | 12 ++++ novu/bridge/app/novu/workflows/basic.tsx | 49 +++++++++++++ novu/bridge/app/novu/workflows/index.ts | 1 + .../api/config/routes/alchemy_notify.yaml | 5 ++ 16 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 databox/api/config/routes/alchemy_notify.yaml create mode 100644 expose/api/config/routes/alchemy_notify.yaml create mode 100644 lib/php/notify-bundle/src/Command/BroadcastNotificationCommand.php create mode 100644 lib/php/notify-bundle/src/Controller/NotificationController.php create mode 100644 lib/php/notify-bundle/src/Form/NotifyForm.php create mode 100644 lib/php/notify-bundle/src/Model/Notification.php create mode 100644 lib/php/notify-bundle/templates/admin/index.html.twig create mode 100644 novu/bridge/app/novu/workflows/basic.tsx create mode 100644 uploader/api/config/routes/alchemy_notify.yaml diff --git a/databox/api/config/routes/alchemy_notify.yaml b/databox/api/config/routes/alchemy_notify.yaml new file mode 100644 index 000000000..b22323e2c --- /dev/null +++ b/databox/api/config/routes/alchemy_notify.yaml @@ -0,0 +1,5 @@ +alchemy_notify: + resource: + path: '@AlchemyNotifyBundle/src/Controller/' + namespace: Alchemy\NotifyBundle\Controller + type: attribute diff --git a/databox/api/src/Controller/Admin/DashboardController.php b/databox/api/src/Controller/Admin/DashboardController.php index 4c4ff2397..d1ca856e7 100644 --- a/databox/api/src/Controller/Admin/DashboardController.php +++ b/databox/api/src/Controller/Admin/DashboardController.php @@ -4,7 +4,9 @@ use Alchemy\AclBundle\Entity\AccessControlEntry; use Alchemy\AdminBundle\Controller\AbstractAdminDashboardController; +use Alchemy\AuthBundle\Security\JwtUser; use Alchemy\ConfiguratorBundle\Entity\ConfiguratorEntry; +use Alchemy\NotifyBundle\Model\Notification; use Alchemy\StorageBundle\Entity\MultipartUpload; use Alchemy\WebhookBundle\Entity\Webhook; use Alchemy\WebhookBundle\Entity\WebhookLog; @@ -128,6 +130,8 @@ public function configureMenuItems(): iterable yield $this->createDevMenu(); yield MenuItem::subMenu('Webhook', 'fas fa-network-wired')->setSubItems($webhookSubMenu); + yield MenuItem::linkToRoute('Notification', 'fas fa-bell', 'alchemy_notify_admin_index'); + yield MenuItem::linkToCrud('Global Config', 'fa fa-gear', ConfiguratorEntry::class); } } diff --git a/expose/api/config/routes/alchemy_notify.yaml b/expose/api/config/routes/alchemy_notify.yaml new file mode 100644 index 000000000..b22323e2c --- /dev/null +++ b/expose/api/config/routes/alchemy_notify.yaml @@ -0,0 +1,5 @@ +alchemy_notify: + resource: + path: '@AlchemyNotifyBundle/src/Controller/' + namespace: Alchemy\NotifyBundle\Controller + type: attribute diff --git a/lib/php/notify-bundle/src/AlchemyNotifyBundle.php b/lib/php/notify-bundle/src/AlchemyNotifyBundle.php index b3dd5e2fe..38a2ddec3 100644 --- a/lib/php/notify-bundle/src/AlchemyNotifyBundle.php +++ b/lib/php/notify-bundle/src/AlchemyNotifyBundle.php @@ -4,7 +4,9 @@ namespace Alchemy\NotifyBundle; +use Alchemy\NotifyBundle\Command\BroadcastNotificationCommand; use Alchemy\NotifyBundle\Command\TestNotificationCommand; +use Alchemy\NotifyBundle\Controller\NotificationController; use Alchemy\NotifyBundle\Notification\MockNotifier; use Alchemy\NotifyBundle\Notification\NotifierInterface; use Alchemy\NotifyBundle\Notification\SymfonyNotifier; @@ -87,6 +89,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $isTest = 'test' === $builder->getParameter('kernel.environment'); $services->alias(NotifierInterface::class, $config['notifier_service'] ?? ($isTest ? MockNotifier::class : SymfonyNotifier::class)); + $services->set(BroadcastNotificationCommand::class); $services->set(TestNotificationCommand::class); + $services->set(NotificationController::class); } } diff --git a/lib/php/notify-bundle/src/Command/BroadcastNotificationCommand.php b/lib/php/notify-bundle/src/Command/BroadcastNotificationCommand.php new file mode 100644 index 000000000..cc8eaa821 --- /dev/null +++ b/lib/php/notify-bundle/src/Command/BroadcastNotificationCommand.php @@ -0,0 +1,45 @@ +addArgument('notificationId', InputArgument::REQUIRED) + ->addArgument('payload', InputArgument::OPTIONAL, 'JSON Payload', '{}') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->notifier->broadcast( + $input->getArgument('notificationId'), + json_decode($input->getArgument('payload') ?? '{}', true, 512, JSON_THROW_ON_ERROR) + ); + + return Command::SUCCESS; + } +} diff --git a/lib/php/notify-bundle/src/Controller/NotificationController.php b/lib/php/notify-bundle/src/Controller/NotificationController.php new file mode 100644 index 000000000..8b791e2c3 --- /dev/null +++ b/lib/php/notify-bundle/src/Controller/NotificationController.php @@ -0,0 +1,68 @@ +em->getRepository(Workspace::class)->findAll(); + + $form = $this->createForm(NotifyForm::class, null, [ + 'workspaces' => $workspaces, + ]); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + /** @var Notification $notification */ + $notification = $form->getData(); + $notificationId = 'basic'; + $payload = [ + 'subject' => $notification->subject, + 'content' => $notification->content, + ]; + + if ($notification->topic) { + $this->notifier->notifyTopic( + $notification->topic, + null, + $notificationId, + $payload + ); + } else { + $this->notifier->broadcast( + $notificationId, + $payload + ); + } + + $request->getSession()->getFlashBag()->add('success', 'Notification sent!'); + + return $this->redirect($request->getRequestUri()); + } + + return $this->render('@AlchemyNotify/admin/index.html.twig', [ + 'form' => $form->createView(), + ]); + } +} diff --git a/lib/php/notify-bundle/src/Form/NotifyForm.php b/lib/php/notify-bundle/src/Form/NotifyForm.php new file mode 100644 index 000000000..9e638fe32 --- /dev/null +++ b/lib/php/notify-bundle/src/Form/NotifyForm.php @@ -0,0 +1,43 @@ + null, + ]; + foreach ($options['workspaces'] as $workspace) { + $choices[$workspace->getName()] = 'ws:'.$workspace->getId(); + } + + $builder + ->add('topic', ChoiceType::class, [ + 'choices' => $choices, + ]) + ->add('subject') + ->add('content', TextareaType::class) + ->add('submit', SubmitType::class) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Notification::class, + ]); + $resolver->setRequired([ + 'workspaces', + ]); + } +} diff --git a/lib/php/notify-bundle/src/Model/Notification.php b/lib/php/notify-bundle/src/Model/Notification.php new file mode 100644 index 000000000..1b751ecaa --- /dev/null +++ b/lib/php/notify-bundle/src/Model/Notification.php @@ -0,0 +1,14 @@ +sentNotifications[] = [ + 'userId' => '*', + 'notificationId' => $notificationId, + 'parameters' => $parameters, + ]; + } + public function sendEmail(string $email, string $notificationId, array $parameters = []): void { $this->sentNotifications[] = [ diff --git a/lib/php/notify-bundle/src/Notification/NotifierInterface.php b/lib/php/notify-bundle/src/Notification/NotifierInterface.php index 6d4e4e36c..00b79c693 100644 --- a/lib/php/notify-bundle/src/Notification/NotifierInterface.php +++ b/lib/php/notify-bundle/src/Notification/NotifierInterface.php @@ -12,6 +12,11 @@ public function notifyUser( array $parameters = [], ): void; + public function broadcast( + string $notificationId, + array $parameters = [], + ): void; + public function sendEmail( string $email, string $notificationId, diff --git a/lib/php/notify-bundle/src/Notification/SymfonyNotifier.php b/lib/php/notify-bundle/src/Notification/SymfonyNotifier.php index 8d1796d1e..5f93aa445 100644 --- a/lib/php/notify-bundle/src/Notification/SymfonyNotifier.php +++ b/lib/php/notify-bundle/src/Notification/SymfonyNotifier.php @@ -28,7 +28,12 @@ public function __construct( public function notifyUser(string $userId, string $notificationId, array $parameters = []): void { $recipient = new NovuSubscriberRecipient($userId); - $this->sendNotification($recipient, $notificationId, $parameters, 'user'); + $this->sendNotification($recipient, $notificationId, $parameters); + } + + public function broadcast(string $notificationId, array $parameters = []): void + { + $this->novuClient->broadcast($notificationId, $parameters); } public function sendEmail(string $email, string $notificationId, array $parameters = []): void diff --git a/lib/php/notify-bundle/src/Service/NovuClient.php b/lib/php/notify-bundle/src/Service/NovuClient.php index 4409ef6e4..b1a7ca698 100644 --- a/lib/php/notify-bundle/src/Service/NovuClient.php +++ b/lib/php/notify-bundle/src/Service/NovuClient.php @@ -56,6 +56,27 @@ public function notifyTopic( ]); } + + public function broadcast( + string $notificationId, + array $parameters = [], + array $options = [], + ): void { + $data = [ + 'name' => $notificationId, + 'payload' => empty($parameters) ? new \stdClass : $parameters, + ]; + + $transactionId = $options['transactionId'] ?? null; + if (null !== $transactionId) { + $data['transactionId'] = $transactionId; + } + + $this->request('POST', '/v1/events/trigger/broadcast', [ + 'json' => $data, + ]); + } + private function request(string $method, string $url, array $options = []): ResponseInterface { return $this->clientExceptionListener->wrapClientRequest(function () use ($method, $url, $options): ResponseInterface { diff --git a/lib/php/notify-bundle/templates/admin/index.html.twig b/lib/php/notify-bundle/templates/admin/index.html.twig new file mode 100644 index 000000000..0fc0401be --- /dev/null +++ b/lib/php/notify-bundle/templates/admin/index.html.twig @@ -0,0 +1,12 @@ +{% extends '@EasyAdmin/page/content.html.twig' %} + +{% block content_title %}Notifications{% endblock %} + +{% block page_actions %} +{% endblock %} + +{% block main %} +
+ {{ form(form) }} +
+{% endblock %} diff --git a/novu/bridge/app/novu/workflows/basic.tsx b/novu/bridge/app/novu/workflows/basic.tsx new file mode 100644 index 000000000..59744a6de --- /dev/null +++ b/novu/bridge/app/novu/workflows/basic.tsx @@ -0,0 +1,49 @@ +import {workflow} from "@novu/framework"; +import {z} from "zod"; +import {Button, render, Section, Text} from "@react-email/components"; +import DefaultEmail, {styles} from "@/app/novu/emails/DefaultEmail"; +import React from "react"; + +export const basic = workflow( + "basic", + async ({step, payload: {subject, content, url}}) => { + await step.inApp("In-App Step", async () => { + return { + subject, + body: content, + redirect: url ? { + url, + } : undefined, + }; + }); + + await step.email("send-email", async () => { + return { + subject, + body: render( +
+ + {content} + + {url ? : null} +
+
+ ), + }; + }); + }, + { + payloadSchema: z.object({ + url: z + .string() + .optional() + .describe("The resource URL"), + subject: z + .string() + .describe("The message subject"), + content: z + .string() + .describe("The message content"), + }) + }, +); diff --git a/novu/bridge/app/novu/workflows/index.ts b/novu/bridge/app/novu/workflows/index.ts index ca233c1ff..68579b95b 100644 --- a/novu/bridge/app/novu/workflows/index.ts +++ b/novu/bridge/app/novu/workflows/index.ts @@ -1,3 +1,4 @@ export * from "./uploader"; export * from "./databox"; export * from "./expose"; +export * from "./basic"; diff --git a/uploader/api/config/routes/alchemy_notify.yaml b/uploader/api/config/routes/alchemy_notify.yaml new file mode 100644 index 000000000..b22323e2c --- /dev/null +++ b/uploader/api/config/routes/alchemy_notify.yaml @@ -0,0 +1,5 @@ +alchemy_notify: + resource: + path: '@AlchemyNotifyBundle/src/Controller/' + namespace: Alchemy\NotifyBundle\Controller + type: attribute