From 139ac5a65577f846e92cbbc9f6bfb5d25ea37b1e Mon Sep 17 00:00:00 2001 From: Adam Bezak Date: Mon, 2 Oct 2023 19:54:53 +0000 Subject: [PATCH] Add command `validate-mail` to validate all email addresses for users in a given time period. --- CHANGELOG.md | 9 +++ Mailer/app/config/config.local.neon.example | 4 + Mailer/app/config/config.test.neon | 1 - .../src/Commands/ValidateCrmEmailsCommand.php | 75 +++++++++++++++++++ .../mailer-module/src/Models/Crm/Client.php | 29 ++++++- 5 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 Mailer/extensions/mailer-module/src/Commands/ValidateCrmEmailsCommand.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b285979..c9078f273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Added string error code to the Subscribe APIs to differentiate between different 404 scenarios. remp/web#2263 - Fix Mailer segment provider users acquiring. Provided segment code needs to be processed before fetching users from database. remp/mnt#114 - Fix New template generator form - broken sorting value `after`. If selected, select box was not shown. remp/helpdesk#2073 +- Added command `crm:validate-emails` to validate all email addresses for users in a given time period. remp/remp#1026 + - You can enable this command in your config.neon if you already defined `crmClient` service: + ``` + services: + console: + setup: + # Enable only if "crmClient" service is available + - add(Remp\MailerModule\Commands\ValidateCrmEmailsCommand()) + ``` ## Archive diff --git a/Mailer/app/config/config.local.neon.example b/Mailer/app/config/config.local.neon.example index e1381a668..1c8129aaf 100644 --- a/Mailer/app/config/config.local.neon.example +++ b/Mailer/app/config/config.local.neon.example @@ -20,6 +20,9 @@ services: # If you're using Beam, you can enable this command to pull conversions from there # - add(Remp\MailerModule\Commands\ProcessConversionStatsCommand()) + # Enable only if "crmClient" service is available + - add Remp\MailerModule\Commands\ValidateCrmEmailsCommand() + # # Internal CRM system. For more details contact Tomas Bella. # authenticator: # factory: Remp\MailerModule\Models\Auth\Authenticator @@ -61,6 +64,7 @@ services: #crmClient: # factory: Remp\MailerModule\Models\Crm\Client(%crm.addr%, %crm.api_token%) + # Setup loggers output commandsLogger: setup: diff --git a/Mailer/app/config/config.test.neon b/Mailer/app/config/config.test.neon index 5dd96cb47..3514de534 100755 --- a/Mailer/app/config/config.test.neon +++ b/Mailer/app/config/config.test.neon @@ -27,7 +27,6 @@ services: mailFactory: setup: - addMailer(Tests\Feature\Mails\TestMailer()) - crmClient: factory: Remp\MailerModule\Models\Crm\Client(%crm.addr%, %crm.api_token%) diff --git a/Mailer/extensions/mailer-module/src/Commands/ValidateCrmEmailsCommand.php b/Mailer/extensions/mailer-module/src/Commands/ValidateCrmEmailsCommand.php new file mode 100644 index 000000000..86913a9b2 --- /dev/null +++ b/Mailer/extensions/mailer-module/src/Commands/ValidateCrmEmailsCommand.php @@ -0,0 +1,75 @@ +setName(self::COMMAND_NAME) + ->setDescription('Validates mail sent in the last (by default) 10 minutes.') + ->addArgument( + "interval", + InputArgument::OPTIONAL, + "How far back the validation interval should extend. By default 10 minutes.", + 'PT10M', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $client = $this->client; + if (is_null($client)) { + $output->writeln("ERROR: CRM client was not initialized, check your config.local.neon."); + return Command::FAILURE; + } + + $interval = $input->getArgument('interval'); + try { + $interval = new DateInterval($interval); + } catch (Exception $e) { + $output->writeln("Failed to parse interval {$interval}:\n" . $e->getMessage()); + return Command::FAILURE; + } + + $deliveredAtFrom = (new DateTime())->sub($interval); + + $emails = $this->mailLogRepository->getTable() + ->select('DISTINCT email') + ->where('delivered_at >= ?', $deliveredAtFrom) + ->fetchPairs(value: 'email'); + + $now = new DateTime(); + + $before = (clone $now)->sub($interval); + $output->writeln(sprintf( + "Validating %d emails, from %s to %s.", + count($emails), + $before->format(\DateTimeInterface::RFC3339), + $now->format(\DateTimeInterface::RFC3339), + )); + + $client->validateMultipleEmails($emails); + return Command::SUCCESS; + } +} diff --git a/Mailer/extensions/mailer-module/src/Models/Crm/Client.php b/Mailer/extensions/mailer-module/src/Models/Crm/Client.php index 9ac6cdbaa..9acbd0028 100644 --- a/Mailer/extensions/mailer-module/src/Models/Crm/Client.php +++ b/Mailer/extensions/mailer-module/src/Models/Crm/Client.php @@ -6,6 +6,7 @@ use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ServerException; +use GuzzleHttp\RequestOptions; use Nette\Utils\Json; use GuzzleHttp\Exception\ClientException; @@ -27,7 +28,7 @@ public function validateEmail(string $email): array { try { $response = $this->client->post('api/v1/users/set-email-validated', [ - 'form_params' => [ + RequestOptions::FORM_PARAMS => [ 'email' => $email, ], ]); @@ -40,7 +41,31 @@ public function validateEmail(string $email): array if (isset($body['code']) && $body['code'] === 'email_not_found') { throw new UserNotFoundException("Unable to find email: {$clientException->getMessage()}"); } + throw new Exception("unable to confirm CRM user: {$clientException->getMessage()}"); + } catch (ServerException $serverException) { + throw new Exception("unable to confirm CRM user: {$serverException->getMessage()}"); + } + } + + /** + * @param string[] $emails + **/ + public function validateMultipleEmails(array $emails): mixed + { + // An empty post request would just waste cpu cycles + if (count($emails) === 0) { + return []; + } + + try { + $response = $this->client->post('api/v2/users/set-email-validated', [ + RequestOptions::JSON => ['emails' => $emails] + ]); + return Json::decode($response->getBody()->getContents(), Json::FORCE_ARRAY); + } catch (ConnectException $connectException) { + throw new Exception("could not connect to CRM: {$connectException->getMessage()}"); + } catch (ClientException $clientException) { throw new Exception("unable to confirm CRM user: {$clientException->getMessage()}"); } catch (ServerException $serverException) { throw new Exception("unable to confirm CRM user: {$serverException->getMessage()}"); @@ -51,7 +76,7 @@ public function userTouch(int $userId): bool { try { $response = $this->client->get('api/v1/users/touch', [ - 'query' => [ + RequestOptions::QUERY => [ 'id' => $userId, ] ]);