From 109a522a6b521018a784954c66a928d31e9425b4 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Sun, 10 Mar 2024 16:31:19 +0100 Subject: [PATCH 01/16] refactor: update composer.json --- composer.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4a818ff..48bd7cb 100644 --- a/composer.json +++ b/composer.json @@ -3,17 +3,25 @@ "description": "Multi-channel Laravel notification sender", "type": "library", "license": "MIT", + "keywords": [ + "laravel", + "sendgrid", + "notifications", + "sendgrid-api", + "laravel-package" + ], "authors": [ { "name": "Chijioke Ibekwe", "email": "ibekwe.chijioke18@gmail.com" } ], + "homepage": "https://github.com/chijioke-ibekwe/raven", "require": { "sendgrid/sendgrid": "~7" }, "require-dev": { - "orchestra/testbench": "7.0", + "orchestra/testbench": "^6.0", "phpunit/phpunit": "^9.6" }, "autoload": { From 6252bc736021e85a97b2af63bd54b7964a9fe1b9 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Sun, 10 Mar 2024 20:27:02 +0100 Subject: [PATCH 02/16] fix: fix send method parameters --- src/Channels/SendGridChannel.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Channels/SendGridChannel.php b/src/Channels/SendGridChannel.php index d66bebb..3472978 100644 --- a/src/Channels/SendGridChannel.php +++ b/src/Channels/SendGridChannel.php @@ -9,15 +9,19 @@ class SendGridChannel { + public function __construct(private SendGrid $sendGrid) + { + // + } + /** * Send the given notification. * * @param mixed $notifiable * @param EmailNotificationSender $sender - * @param SendGrid $sendGrid * @return void */ - public function send(mixed $notifiable, EmailNotificationSender $sender, SendGrid $sendGrid): void + public function send(mixed $notifiable, EmailNotificationSender $sender): void { try { @@ -26,14 +30,14 @@ public function send(mixed $notifiable, EmailNotificationSender $sender, SendGri $email->setOpenTracking(true, "--sub--"); $email->setFrom(config('raven.mail.from.address'), config('raven.mail.from.name')); - $response = $sendGrid->send($email); + $response = $this->sendGrid->send($email); if ($response->statusCode() != '202') { Log::info("Mail success response: " . $response->body()); } } catch (Exception $e) { - Log::error("Failed sending mail to $email: " . $e->getMessage()); + Log::error("Failed sending mail: " . $e->getMessage()); } } } \ No newline at end of file From cd57e67a3536610695710b87ad9a32cf980f6536 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 00:33:50 +0100 Subject: [PATCH 03/16] feat: register amazon ses client and channel --- src/RavenServiceProvider.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/RavenServiceProvider.php b/src/RavenServiceProvider.php index 0b6bd3b..557243b 100644 --- a/src/RavenServiceProvider.php +++ b/src/RavenServiceProvider.php @@ -2,6 +2,9 @@ namespace ChijiokeIbekwe\Raven; +use Aws\Ses\SesClient; +use ChijiokeIbekwe\Raven\Channels\AmazonSesChannel; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; @@ -48,12 +51,24 @@ public function boot(): void $this->registerRoutes(); $this->app->singleton(SendGrid::class, function ($app) { - return new SendGrid(config('raven.api-key.sendgrid')); + return new SendGrid(config('raven.credentials.sendgrid')); + }); + + $this->app->singleton(SesClient::class, function ($app) { + return new SesClient([ + 'credentials' => Arr::except(config('raven.credentials.ses'), 'region'), + 'version' => 'latest', + 'region' => config('raven.credentials.ses.region') + ]); }); Notification::extend('sendgrid', function ($app) { return new SendGridChannel(); }); + + Notification::extend('ses', function ($app) { + return new AmazonSesChannel(); + }); } protected function registerRoutes(): void From 469afc130e3736fe54f13d15f58254b73256c139 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 00:44:12 +0100 Subject: [PATCH 04/16] feat: register amazon ses client and channel --- src/RavenServiceProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RavenServiceProvider.php b/src/RavenServiceProvider.php index 557243b..a4b7f83 100644 --- a/src/RavenServiceProvider.php +++ b/src/RavenServiceProvider.php @@ -51,14 +51,14 @@ public function boot(): void $this->registerRoutes(); $this->app->singleton(SendGrid::class, function ($app) { - return new SendGrid(config('raven.credentials.sendgrid')); + return new SendGrid(config('raven.providers.sendgrid.key')); }); $this->app->singleton(SesClient::class, function ($app) { return new SesClient([ - 'credentials' => Arr::except(config('raven.credentials.ses'), 'region'), + 'credentials' => Arr::only(config('raven.providers.ses'), ['key', 'secret']), 'version' => 'latest', - 'region' => config('raven.credentials.ses.region') + 'region' => config('raven.providers.ses.region') ]); }); From 55de05ecd46a26cb7e94c073782addd8a172b084 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 01:10:29 +0100 Subject: [PATCH 05/16] feat: add configs for ses channel --- config/raven.php | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/config/raven.php b/config/raven.php index e265cbc..923e91a 100644 --- a/config/raven.php +++ b/config/raven.php @@ -2,20 +2,31 @@ return [ - 'notification-service' => [ + 'default' => [ 'email' => env('EMAIL_NOTIFICATION_PROVIDER', 'sendgrid'), 'sms' => env('SMS_NOTIFICATION_PROVIDER', 'nexmo') ], - 'api-key' => [ - 'sendgrid' => env('SENDGRID_API_KEY') + 'providers' => [ + 'sendgrid' => [ + 'key' => env('SENDGRID_API_KEY') + ], + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'template_source' => env('AWS_SES_TEMPLATE_SOURCE', 'sendgrid'), + 'template_directory' => env('AWS_SES_TEMPLATE_DIRECTORY', 'resources/views/emails') + ] ], - 'mail' => [ - 'from' => [ - 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), - 'name' => env('MAIL_FROM_NAME', 'Example'), - ] + 'customizations' => [ + 'mail' => [ + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ] + ], ], 'api' => [ From 7192c40557f381ec3f3c559631db7d3e428f9c00 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 06:13:48 +0100 Subject: [PATCH 06/16] fix: update config path reference --- src/Listeners/RavenListener.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Listeners/RavenListener.php b/src/Listeners/RavenListener.php index 8cb7dd3..2ecea8f 100644 --- a/src/Listeners/RavenListener.php +++ b/src/Listeners/RavenListener.php @@ -20,6 +20,7 @@ class RavenListener { const EMAIL_PATTERN = '#^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$#'; const PHONE_PATTERN = '#^\+?[0-9\s-()]+$#'; + /** * Create the event listener. */ @@ -44,7 +45,10 @@ public function handle(Raven $event): void $this->sendNotifications($data, $context); } - private function sendNotifications(NotificationData $data, NotificationContext $context) + /** + * @throws \Throwable + */ + private function sendNotifications(NotificationData $data, NotificationContext $context): void { $factory = new ChannelSenderFactory($data, $context); $channels = $context->notification_channels; @@ -81,19 +85,19 @@ private function sendNotifications(NotificationData $data, NotificationContext $ } } - private function resolveRouteWithChannelSender($recipient, $channel_sender) + private function resolveRouteWithChannelSender($recipient, $channel_sender): void { $sender_class = get_class($channel_sender); switch($sender_class) { case EmailNotificationSender::class: if(preg_match(self::EMAIL_PATTERN, $recipient)) { - Notification::route(config('raven.notification-service.email'), $recipient)->notify($channel_sender); + Notification::route(config('raven.default.email'), $recipient)->notify($channel_sender); }; return; case SmsNotificationSender::class: if(preg_match(self::PHONE_PATTERN, $recipient)) { - Notification::route(config('raven.notification-service.sms'), $recipient)->notify($channel_sender); + Notification::route(config('raven.default.sms'), $recipient)->notify($channel_sender); }; return; case DatabaseNotificationSender::class: From c8ba7e1282e7a0f97b3e6dd5d90516e115a8bfc8 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 11:11:49 +0100 Subject: [PATCH 07/16] feat: install aws sdk and php mailer --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 48bd7cb..23c0c3e 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,9 @@ ], "homepage": "https://github.com/chijioke-ibekwe/raven", "require": { - "sendgrid/sendgrid": "~7" + "sendgrid/sendgrid": "~7", + "aws/aws-sdk-php": "^3.300", + "phpmailer/phpmailer": "^6.9" }, "require-dev": { "orchestra/testbench": "^6.0", From fb058e4887702b2b4461940681fc2e3e1f49aa85 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 11:12:55 +0100 Subject: [PATCH 08/16] feat: implement amazon ses email notification channel --- src/Channels/AmazonSesChannel.php | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/Channels/AmazonSesChannel.php diff --git a/src/Channels/AmazonSesChannel.php b/src/Channels/AmazonSesChannel.php new file mode 100644 index 0000000..8b88b2c --- /dev/null +++ b/src/Channels/AmazonSesChannel.php @@ -0,0 +1,111 @@ +sesClient = app(SesClient::class); + $this->sendGrid = app(SendGrid::class); + } + + /** + * Send the given notification. + * + * @param mixed $notifiable + * @param EmailNotificationSender $emailNotification + * @return void + * @throws Exception + */ + public function send(mixed $notifiable, EmailNotificationSender $emailNotification): void + { + $email = $emailNotification->toAmazonSes($notifiable); + + $sender = config('raven.customizations.mail.from'); + $email->setFrom($sender['address'], $sender['name']); + + $template_source = config('raven.providers.ses.template_source'); + if($template_source !== 'sendgrid') { + Log::error("Template source $template_source not currently supported"); + throw new Exception("Template source $template_source not currently supported"); + } + + $template_response = $this->getSendGridTemplateContent($emailNotification); + + $params = $emailNotification->notificationData->getParams(); + $clean_html = $this->cleanTemplate($template_response['html_content'], $params); + $clean_plain = $this->cleanTemplate($template_response['plain_content'], $params); + + $email->Subject = $template_response['subject']; + $email->Body = $clean_html; + $email->AltBody = $clean_plain; + + if (!$email->preSend()) { + Log::error("Failed sending mail: " . $email->ErrorInfo); + throw new Exception($email->ErrorInfo); + } else { + $message = $email->getSentMIMEMessage(); + } + + try { + $result = $this->sesClient->sendRawEmail([ + 'RawMessage' => [ + 'Data' => $message + ] + ]); + Log::info($result); + } catch (SesException $error) { + Log::error("Failed sending mail: " . $error->getAwsErrorMessage()); + } + } + + /** + * @throws Exception + */ + private function getSendGridTemplateContent(EmailNotificationSender $emailNotification): array + { + try { + $template_id = $emailNotification->notificationContext->email_template_id; + $response = $this->sendGrid->client->templates()->_($template_id)->get(); + + if(!($response->statusCode() >= '200' && $response->statusCode() < 300)) { + throw new Exception("SendGrid server returned error response"); + } + + $body_json = $response->body(); + $body_arr = json_decode($body_json, true); + $subject = $body_arr['versions'][0]['subject']; + $html_content = $body_arr['versions'][0]['html_content']; + $plain_content = $body_arr['versions'][0]['plain_content']; + + return [ + 'subject' => $subject, + 'html_content' => $html_content, + 'plain_content' => $plain_content + ]; + + } catch (Exception $e) { + Log::error("Failed sending mail: " . $e->getMessage()); + throw new Exception($e); + } + } + + private function cleanTemplate($template, $data) + { + foreach ($data as $key => $value) { + $template = str_replace('{{' . $key . '}}', $value, $template); + } + return $template; + } +} \ No newline at end of file From 11ee992f26e4c3c4f2b467fa98c5bd6532915417 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 11:14:08 +0100 Subject: [PATCH 09/16] refactor: clean up send grid channel --- src/Channels/SendGridChannel.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Channels/SendGridChannel.php b/src/Channels/SendGridChannel.php index 3472978..2039fb2 100644 --- a/src/Channels/SendGridChannel.php +++ b/src/Channels/SendGridChannel.php @@ -9,30 +9,32 @@ class SendGridChannel { - public function __construct(private SendGrid $sendGrid) + private SendGrid $sendGrid; + + public function __construct() { - // + $this->sendGrid = app(SendGrid::class); } /** * Send the given notification. * * @param mixed $notifiable - * @param EmailNotificationSender $sender + * @param EmailNotificationSender $emailNotification * @return void */ - public function send(mixed $notifiable, EmailNotificationSender $sender): void + public function send(mixed $notifiable, EmailNotificationSender $emailNotification): void { - try { - $email = $sender->toSendgrid($notifiable); + $email = $emailNotification->toSendgrid($notifiable); $email->setClickTracking(true, true); $email->setOpenTracking(true, "--sub--"); - $email->setFrom(config('raven.mail.from.address'), config('raven.mail.from.name')); + $sender = config('raven.customizations.mail.from'); + $email->setFrom($sender['address'], $sender['name']); $response = $this->sendGrid->send($email); - if ($response->statusCode() != '202') { + if($response->statusCode() >= '200' && $response->statusCode() < 300) { Log::info("Mail success response: " . $response->body()); } From db9b9e2f6144b1ef0f05c4afb53799084f1962ef Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 11:15:48 +0100 Subject: [PATCH 10/16] feat: add toAmazonSes method in email notification sender --- src/Notifications/EmailNotificationSender.php | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/Notifications/EmailNotificationSender.php b/src/Notifications/EmailNotificationSender.php index c0373a4..4364240 100644 --- a/src/Notifications/EmailNotificationSender.php +++ b/src/Notifications/EmailNotificationSender.php @@ -8,6 +8,8 @@ use ChijiokeIbekwe\Raven\Data\NotificationData; use ChijiokeIbekwe\Raven\Exceptions\RavenInvalidDataException; use ChijiokeIbekwe\Raven\Models\NotificationContext; +use PHPMailer\PHPMailer\Exception; +use PHPMailer\PHPMailer\PHPMailer; use SendGrid\Mail\Attachment; use SendGrid\Mail\Mail; use SendGrid\Mail\TypeException; @@ -24,7 +26,7 @@ public function __construct(public readonly NotificationData $notificationDat public function via(mixed $notifiable): array { - return [config('raven.notification-service.email')]; + return [config('raven.default.email')]; } /** @@ -36,12 +38,10 @@ public function via(mixed $notifiable): array */ public function toSendgrid(mixed $notifiable): ?Mail { - $provider = config('raven.notification-service.email'); - $route = $notifiable->routeNotificationFor('mail'); if (!$route) { - throw new RavenInvalidDataException("Missing route for $provider"); + throw new RavenInvalidDataException("Missing route for mail"); } $email = new Mail(); @@ -74,6 +74,39 @@ public function toSendgrid(mixed $notifiable): ?Mail { return $email; } + /** + * Get the PHPMailer object for Amazon SES channel. + * + * @param mixed $notifiable + * @return PHPMailer|null + * @throws RavenInvalidDataException|TypeException|Exception + */ + public function toAmazonSes(mixed $notifiable): ?PHPMailer { + + $route = $notifiable->routeNotificationFor('mail'); + + if (!$route) { + throw new RavenInvalidDataException("Missing route for mail"); + } + + $email = new PHPMailer(true); + $email->addAddress($route); + + if(!empty($this->notificationData->getCcs())){ + foreach ($this->notificationData->getCcs() as $email){ + $email->addCc($email); + } + } + + if(!empty($this->notificationData->getAttachmentUrls())) { + foreach ($this->notificationData->getAttachmentUrls() as $url){ + $email->addAttachment($url); + } + } + + return $email; + } + /** * @throws \Throwable */ From 53da33cbc04157f84d4d42c0154bdeaf55f098bc Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 11:16:38 +0100 Subject: [PATCH 11/16] refactor: clean up config in sms notificaton sender --- src/Notifications/SmsNotificationSender.php | 2 +- tests/Feature/NotificationTest.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Notifications/SmsNotificationSender.php b/src/Notifications/SmsNotificationSender.php index b4fae53..2b8e1c2 100644 --- a/src/Notifications/SmsNotificationSender.php +++ b/src/Notifications/SmsNotificationSender.php @@ -19,7 +19,7 @@ public function __construct(public readonly NotificationData $notificationDTO } public function via($notifiable): array { - return ['raven.notification-service.sms']; + return ['raven.default.sms']; } public function validateNotification() diff --git a/tests/Feature/NotificationTest.php b/tests/Feature/NotificationTest.php index 088c0fc..ec598f1 100644 --- a/tests/Feature/NotificationTest.php +++ b/tests/Feature/NotificationTest.php @@ -22,8 +22,7 @@ class NotificationTest extends TestCase public function getEnvironmentSetUp($app): void { - $app['config']->set('raven.notification-service.email', 'sendgrid'); - $app['config']->set('raven.notification-service.database', 'database'); + $app['config']->set('raven.default.email', 'sendgrid'); // run the up() method (perform the migration) (new \CreateNotificationContextsTable)->up(); From fee0125d096c4b61fbea3087d28ce9627d0e5552 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Mon, 11 Mar 2024 11:29:37 +0100 Subject: [PATCH 12/16] refactor: rename notification data class --- src/Data/{NotificationData.php => Scroll.php} | 4 +- src/Events/Raven.php | 4 +- src/Listeners/RavenListener.php | 10 +- .../DatabaseNotificationSender.php | 8 +- src/Notifications/EmailNotificationSender.php | 22 ++-- src/Notifications/SmsNotificationSender.php | 4 +- src/Services/ChannelSenderFactory.php | 12 +- tests/Feature/NotificationTest.php | 114 +++++++++--------- 8 files changed, 89 insertions(+), 89 deletions(-) rename src/Data/{NotificationData.php => Scroll.php} (98%) diff --git a/src/Data/NotificationData.php b/src/Data/Scroll.php similarity index 98% rename from src/Data/NotificationData.php rename to src/Data/Scroll.php index 61e7b0a..ba9b4ce 100644 --- a/src/Data/NotificationData.php +++ b/src/Data/Scroll.php @@ -5,7 +5,7 @@ use Illuminate\Notifications\Notifiable; use ChijiokeIbekwe\Raven\Exceptions\RavenInvalidDataException; -class NotificationData +class Scroll { /** @@ -151,7 +151,7 @@ public function setParams(array $params): void } /** - * @param mixed $attachments + * @param mixed $attachmentUrls * @return void */ public function setAttachmentUrls(mixed $attachmentUrls): void diff --git a/src/Events/Raven.php b/src/Events/Raven.php index 00333e9..da74b1b 100644 --- a/src/Events/Raven.php +++ b/src/Events/Raven.php @@ -4,7 +4,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Foundation\Events\Dispatchable; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; class Raven { @@ -13,7 +13,7 @@ class Raven /** * Create a new event instance. */ - public function __construct(public NotificationData $notificationData) + public function __construct(public Scroll $scroll) { // } diff --git a/src/Listeners/RavenListener.php b/src/Listeners/RavenListener.php index 2ecea8f..405ff4d 100644 --- a/src/Listeners/RavenListener.php +++ b/src/Listeners/RavenListener.php @@ -2,7 +2,7 @@ namespace ChijiokeIbekwe\Raven\Listeners; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use ChijiokeIbekwe\Raven\Events\Raven; @@ -48,9 +48,9 @@ public function handle(Raven $event): void /** * @throws \Throwable */ - private function sendNotifications(NotificationData $data, NotificationContext $context): void + private function sendNotifications(Scroll $scroll, NotificationContext $context): void { - $factory = new ChannelSenderFactory($data, $context); + $factory = new ChannelSenderFactory($scroll, $context); $channels = $context->notification_channels; foreach($channels as $channel){ @@ -60,7 +60,7 @@ private function sendNotifications(NotificationData $data, NotificationContext $ $channel_sender = $factory->getSender($channel_type); - $recipients = $data->getRecipients(); + $recipients = $scroll->getRecipients(); if(!$channel_sender) { Log::error("Notification channel $channel_type->name is not currently supported"); @@ -69,7 +69,7 @@ private function sendNotifications(NotificationData $data, NotificationContext $ Log::info("Sending notification for context $context->name through channel $channel_type->name"); - if(!$data->getHasOnDemand()) { + if(!$scroll->getHasOnDemand()) { Notification::send($recipients, $channel_sender); continue; } diff --git a/src/Notifications/DatabaseNotificationSender.php b/src/Notifications/DatabaseNotificationSender.php index 03411ad..6c17f62 100644 --- a/src/Notifications/DatabaseNotificationSender.php +++ b/src/Notifications/DatabaseNotificationSender.php @@ -5,7 +5,7 @@ use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Bus\Queueable; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; use ChijiokeIbekwe\Raven\Exceptions\RavenInvalidDataException; use ChijiokeIbekwe\Raven\Models\NotificationContext; @@ -13,7 +13,7 @@ class DatabaseNotificationSender extends Notification implements ShouldQueue, IN { use Queueable; - public function __construct(public readonly NotificationData $notificationData, + public function __construct(public readonly Scroll $scroll, public readonly NotificationContext $notificationContext) { // @@ -42,14 +42,14 @@ public function databaseType(object $notifiable): string */ public function toDatabase(object $notifiable): array { - $param_keys = array_keys($this->notificationData->getParams()); + $param_keys = array_keys($this->scroll->getParams()); for ($i = 0; $i < count($param_keys); $i++) { $old_key = $param_keys[$i]; $param_keys[$i] = '{' . $old_key . '}'; } - $param_values = array_values($this->notificationData->getParams()); + $param_values = array_values($this->scroll->getParams()); $body = str_replace($param_keys, $param_values, $this->notificationContext->body); diff --git a/src/Notifications/EmailNotificationSender.php b/src/Notifications/EmailNotificationSender.php index 4364240..361d44b 100644 --- a/src/Notifications/EmailNotificationSender.php +++ b/src/Notifications/EmailNotificationSender.php @@ -5,7 +5,7 @@ use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Bus\Queueable; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; use ChijiokeIbekwe\Raven\Exceptions\RavenInvalidDataException; use ChijiokeIbekwe\Raven\Models\NotificationContext; use PHPMailer\PHPMailer\Exception; @@ -18,7 +18,7 @@ class EmailNotificationSender extends Notification implements ShouldQueue, INoti { use Queueable; - public function __construct(public readonly NotificationData $notificationData, + public function __construct(public readonly Scroll $scroll, public readonly NotificationContext $notificationContext) { // @@ -48,16 +48,16 @@ public function toSendgrid(mixed $notifiable): ?Mail { $email->setTemplateId($this->notificationContext->email_template_id); $email->addTo($route); - if(!empty($this->notificationData->getCcs())){ - $email->addCcs($this->notificationData->getCcs()); + if(!empty($this->scroll->getCcs())){ + $email->addCcs($this->scroll->getCcs()); } - $substitutions = $this->notificationData->getParams(); + $substitutions = $this->scroll->getParams(); $email->addDynamicTemplateDatas($substitutions); - if(!empty($this->notificationData->getAttachmentUrls())) { + if(!empty($this->scroll->getAttachmentUrls())) { $attachments = []; - foreach ($this->notificationData->getAttachmentUrls() as $url){ + foreach ($this->scroll->getAttachmentUrls() as $url){ $attachment = new Attachment(); $filename = basename($url); $file_encoded = base64_encode(file_get_contents($url)); @@ -92,14 +92,14 @@ public function toAmazonSes(mixed $notifiable): ?PHPMailer { $email = new PHPMailer(true); $email->addAddress($route); - if(!empty($this->notificationData->getCcs())){ - foreach ($this->notificationData->getCcs() as $email){ + if(!empty($this->scroll->getCcs())){ + foreach ($this->scroll->getCcs() as $email){ $email->addCc($email); } } - if(!empty($this->notificationData->getAttachmentUrls())) { - foreach ($this->notificationData->getAttachmentUrls() as $url){ + if(!empty($this->scroll->getAttachmentUrls())) { + foreach ($this->scroll->getAttachmentUrls() as $url){ $email->addAttachment($url); } } diff --git a/src/Notifications/SmsNotificationSender.php b/src/Notifications/SmsNotificationSender.php index 2b8e1c2..a198729 100644 --- a/src/Notifications/SmsNotificationSender.php +++ b/src/Notifications/SmsNotificationSender.php @@ -5,14 +5,14 @@ use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Bus\Queueable; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; use ChijiokeIbekwe\Raven\Models\NotificationContext; class SmsNotificationSender extends Notification implements ShouldQueue, INotificationSender { use Queueable; - public function __construct(public readonly NotificationData $notificationDTO, + public function __construct(public readonly Scroll $scroll, public readonly NotificationContext $notificationContext) { // diff --git a/src/Services/ChannelSenderFactory.php b/src/Services/ChannelSenderFactory.php index d56bae3..4f88e84 100644 --- a/src/Services/ChannelSenderFactory.php +++ b/src/Services/ChannelSenderFactory.php @@ -5,7 +5,7 @@ use ChijiokeIbekwe\Raven\Enums\ChannelType; use ChijiokeIbekwe\Raven\Notifications\DatabaseNotificationSender; use ChijiokeIbekwe\Raven\Notifications\EmailNotificationSender; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; use ChijiokeIbekwe\Raven\Models\NotificationContext; use ChijiokeIbekwe\Raven\Notifications\SmsNotificationSender; @@ -19,16 +19,16 @@ class ChannelSenderFactory /** - * @param NotificationData $notificationData + * @param Scroll $scroll * @param NotificationContext $notificationContext */ - public function __construct(private readonly NotificationData $notificationData, + public function __construct(private readonly Scroll $scroll, private readonly NotificationContext $notificationContext) { - $email_sender = new EmailNotificationSender($this->notificationData, $this->notificationContext); - $sms_sender = new SmsNotificationSender($this->notificationData, $this->notificationContext); - $database_sender = new DatabaseNotificationSender($this->notificationData, $this->notificationContext); + $email_sender = new EmailNotificationSender($this->scroll, $this->notificationContext); + $sms_sender = new SmsNotificationSender($this->scroll, $this->notificationContext); + $database_sender = new DatabaseNotificationSender($this->scroll, $this->notificationContext); $this->sender_store = [ diff --git a/tests/Feature/NotificationTest.php b/tests/Feature/NotificationTest.php index ec598f1..2505b02 100644 --- a/tests/Feature/NotificationTest.php +++ b/tests/Feature/NotificationTest.php @@ -4,7 +4,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Notification; -use ChijiokeIbekwe\Raven\Data\NotificationData; +use ChijiokeIbekwe\Raven\Data\Scroll; use ChijiokeIbekwe\Raven\Events\Raven; use ChijiokeIbekwe\Raven\Exceptions\RavenEntityNotFoundException; use ChijiokeIbekwe\Raven\Exceptions\RavenInvalidDataException; @@ -51,16 +51,16 @@ public function test_that_email_notifications_are_sent_when_the_raven_listener_r $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-created'); - $data->setRecipients($user); - $data->setCcs(["email@raven.com" => "Jane Doe"]); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-created'); + $scroll->setRecipients($user); + $scroll->setCcs(["email@raven.com" => "Jane Doe"]); + $scroll->setParams([ 'booking_id' => 'JET12345' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); Notification::assertCount(1); @@ -68,11 +68,11 @@ public function test_that_email_notifications_are_sent_when_the_raven_listener_r Notification::assertSentTo( $user, EmailNotificationSender::class, - function (EmailNotificationSender $notification) use ($user, $data, $context) { + function (EmailNotificationSender $notification) use ($user, $scroll, $context) { $mail = $notification->toSendgrid($user); $via = $notification->via($user); - return $notification->notificationData === $data && + return $notification->scroll === $scroll && $notification->notificationContext->name === $context->name && $mail->getTemplateId()->getTemplateId() === 'sendgrid-template' && $mail->getDynamicTemplateDatas() === [ @@ -106,16 +106,16 @@ public function test_that_email_notifications_are_sent_when_the_an_email_address $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-created'); - $data->setRecipients([$user, 'jane.doe@raven.com']); - $data->setCcs(["email@raven.com" => "Jane Doe"]); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-created'); + $scroll->setRecipients([$user, 'jane.doe@raven.com']); + $scroll->setCcs(["email@raven.com" => "Jane Doe"]); + $scroll->setParams([ 'booking_id' => 'JET12345' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); Notification::assertCount(2); @@ -123,11 +123,11 @@ public function test_that_email_notifications_are_sent_when_the_an_email_address Notification::assertSentTo( $user, EmailNotificationSender::class, - function (EmailNotificationSender $notification) use ($user, $data, $context) { + function (EmailNotificationSender $notification) use ($user, $scroll, $context) { $mail = $notification->toSendgrid($user); $via = $notification->via($user); - return $notification->notificationData === $data && + return $notification->scroll === $scroll && $notification->notificationContext->name === $context->name && $mail->getTemplateId()->getTemplateId() === 'sendgrid-template' && $mail->getDynamicTemplateDatas() === [ @@ -163,16 +163,16 @@ public function test_that_database_notifications_are_sent_when_the_raven_listene $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-verified'); - $data->setRecipients($user); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-verified'); + $scroll->setRecipients($user); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); Notification::assertCount(1); @@ -180,11 +180,11 @@ public function test_that_database_notifications_are_sent_when_the_raven_listene Notification::assertSentTo( $user, DatabaseNotificationSender::class, - function (DatabaseNotificationSender $notification) use ($user, $data, $context) { + function (DatabaseNotificationSender $notification) use ($user, $scroll, $context) { $content = $notification->toDatabase($user); $via = $notification->via($user); - return $notification->notificationData === $data && + return $notification->scroll === $scroll && $notification->notificationContext->name === $context->name && data_get($content, 'title') === 'Verification' && data_get($content, 'body') === 'User with id 345 has been verified on the platform on 11-12-2023 10:51' && @@ -209,15 +209,15 @@ public function test_that_exception_is_thrown_when_notification_context_name_is_ 'email' => 'john.doe@raven.com' ])->get(0); - $data = new NotificationData(); - $data->setRecipients($user); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setRecipients($user); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } @@ -234,16 +234,16 @@ public function test_that_exception_is_thrown_when_notification_context_name_doe 'email' => 'john.doe@raven.com' ])->get(0); - $data = new NotificationData(); - $data->setContextName('user-verified'); - $data->setRecipients($user); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-verified'); + $scroll->setRecipients($user); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } @@ -268,16 +268,16 @@ public function test_that_exception_is_thrown_when_email_notification_context_ha $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-updated'); - $data->setRecipients($user); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-updated'); + $scroll->setRecipients($user); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } @@ -303,16 +303,16 @@ public function test_that_exception_is_thrown_when_database_notification_context $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-updated'); - $data->setRecipients($user); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-updated'); + $scroll->setRecipients($user); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } @@ -338,16 +338,16 @@ public function test_that_exception_is_thrown_when_database_notification_context $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-updated'); - $data->setRecipients($user); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-updated'); + $scroll->setRecipients($user); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } @@ -373,15 +373,15 @@ public function test_that_exception_is_thrown_when_recipients_are_not_provided_i $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-created'); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-created'); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } @@ -405,16 +405,16 @@ public function test_that_exception_is_thrown_when_a_non_notifiable_recipient_is $context->notification_channels()->attach($channel->id); - $data = new NotificationData(); - $data->setContextName('user-created'); - $data->setRecipients($channel); - $data->setParams([ + $scroll = new Scroll(); + $scroll->setContextName('user-created'); + $scroll->setRecipients($channel); + $scroll->setParams([ 'user_id' => '345', 'date_time' => '11-12-2023 10:51' ]); (new RavenListener())->handle( - new Raven($data) + new Raven($scroll) ); } } From bd7005def2fc01e62cd9b9895600d64b64ba726d Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Tue, 12 Mar 2024 13:51:26 +0100 Subject: [PATCH 13/16] test: fix notification tests --- ...nTest.php => SendGridNotificationTest.php} | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) rename tests/Feature/{NotificationTest.php => SendGridNotificationTest.php} (90%) diff --git a/tests/Feature/NotificationTest.php b/tests/Feature/SendGridNotificationTest.php similarity index 90% rename from tests/Feature/NotificationTest.php rename to tests/Feature/SendGridNotificationTest.php index 2505b02..a533336 100644 --- a/tests/Feature/NotificationTest.php +++ b/tests/Feature/SendGridNotificationTest.php @@ -16,7 +16,7 @@ use ChijiokeIbekwe\Raven\Tests\TestCase; use ChijiokeIbekwe\Raven\Tests\Utilities\User; -class NotificationTest extends TestCase +class SendGridNotificationTest extends TestCase { use RefreshDatabase; @@ -37,15 +37,15 @@ public function test_that_email_notifications_are_sent_when_the_raven_listener_r Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'email_template_id' => 'sendgrid-template', 'name' => 'user-created' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'EMAIL')->first(); @@ -63,8 +63,6 @@ public function test_that_email_notifications_are_sent_when_the_raven_listener_r new Raven($scroll) ); - Notification::assertCount(1); - Notification::assertSentTo( $user, EmailNotificationSender::class, @@ -92,15 +90,15 @@ public function test_that_email_notifications_are_sent_when_the_an_email_address Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'email_template_id' => 'sendgrid-template', 'name' => 'user-created' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'EMAIL')->first(); @@ -118,8 +116,6 @@ public function test_that_email_notifications_are_sent_when_the_an_email_address new Raven($scroll) ); - Notification::assertCount(2); - Notification::assertSentTo( $user, EmailNotificationSender::class, @@ -147,17 +143,17 @@ public function test_that_database_notifications_are_sent_when_the_raven_listene Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'name' => 'user-verified', 'title' => 'Verification', 'body' => 'User with id {user_id} has been verified on the platform on {date_time}', 'type' => 'user' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'DATABASE')->first(); @@ -175,8 +171,6 @@ public function test_that_database_notifications_are_sent_when_the_raven_listene new Raven($scroll) ); - Notification::assertCount(1); - Notification::assertSentTo( $user, DatabaseNotificationSender::class, @@ -204,10 +198,10 @@ public function test_that_exception_is_thrown_when_notification_context_name_is_ Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); $scroll = new Scroll(); $scroll->setRecipients($user); @@ -229,10 +223,10 @@ public function test_that_exception_is_thrown_when_notification_context_name_doe Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); $scroll = new Scroll(); $scroll->setContextName('user-verified'); @@ -255,14 +249,14 @@ public function test_that_exception_is_thrown_when_email_notification_context_ha Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'name' => 'user-updated' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'EMAIL')->first(); @@ -289,15 +283,15 @@ public function test_that_exception_is_thrown_when_database_notification_context Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'name' => 'user-updated', 'body' => 'User with id {user_id} has been updated on {date_time}' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'DATABASE')->first(); @@ -324,15 +318,15 @@ public function test_that_exception_is_thrown_when_database_notification_context Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'name' => 'user-updated', 'title' => 'User Updated' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'DATABASE')->first(); @@ -359,15 +353,15 @@ public function test_that_exception_is_thrown_when_recipients_are_not_provided_i Notification::fake(); - $user = User::factory(1)->make([ + $user = User::factory()->make([ 'name' => 'John Doe', 'email' => 'john.doe@raven.com' - ])->get(0); + ]); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'email_template_id' => 'sendgrid-template', 'name' => 'user-created' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'EMAIL')->first(); @@ -396,10 +390,10 @@ public function test_that_exception_is_thrown_when_a_non_notifiable_recipient_is Notification::fake(); - $context = NotificationContext::factory(1)->create([ + $context = NotificationContext::factory()->create([ 'email_template_id' => 'sendgrid-template', 'name' => 'user-created' - ])->get(0); + ]); $channel = NotificationChannel::where('type', 'EMAIL')->first(); From 34727271440d7e4d6f1e1ba3f1b6a324cf4c16bd Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Tue, 12 Mar 2024 13:52:04 +0100 Subject: [PATCH 14/16] feat: add phpunit config --- phpunit.xml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 phpunit.xml diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..05d8858 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,32 @@ + + + + + ./tests/Unit + + + + ./tests/Feature + + + + + + + + + + + + src/ + + + \ No newline at end of file From 93553ed52ca654a02c67f799f8e091040ee330e3 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Tue, 12 Mar 2024 13:53:00 +0100 Subject: [PATCH 15/16] chore: update gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 43915ef..7a911c6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ build composer.lock coverage docs -phpunit.xml phpstan.neon testbench.yaml vendor From d151a0d497fd2dde6ae3b2866ae126c924d22999 Mon Sep 17 00:00:00 2001 From: Chijioke Ibekwe Date: Tue, 12 Mar 2024 14:30:49 +0100 Subject: [PATCH 16/16] style: clean up --- src/Listeners/RavenListener.php | 2 +- tests/Utilities/UserFactory.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Listeners/RavenListener.php b/src/Listeners/RavenListener.php index 405ff4d..de8273e 100644 --- a/src/Listeners/RavenListener.php +++ b/src/Listeners/RavenListener.php @@ -35,7 +35,7 @@ public function __construct() */ public function handle(Raven $event): void { - $data = $event->notificationData; + $data = $event->scroll; $context_name = $data->getContextName(); $context = NotificationContext::where('name', $context_name)->first(); diff --git a/tests/Utilities/UserFactory.php b/tests/Utilities/UserFactory.php index f046b85..b5bb8bd 100644 --- a/tests/Utilities/UserFactory.php +++ b/tests/Utilities/UserFactory.php @@ -18,8 +18,8 @@ class UserFactory extends Factory public function definition(): array { return [ - 'name' => fake()->name(), - 'email' => fake()->unique()->safeEmail(), + 'name' => $this->faker->name(), + 'email' => $this->faker->email, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10),