appwrite/src/Appwrite/Platform/Workers/Messaging.php

695 lines
26 KiB
PHP
Raw Normal View History

2023-05-29 16:32:33 +00:00
<?php
namespace Appwrite\Platform\Workers;
2024-02-12 01:18:19 +00:00
use Appwrite\Event\Usage;
use Appwrite\Messaging\Status as MessageStatus;
use Swoole\Runtime;
2023-05-29 16:32:33 +00:00
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
2024-01-30 09:19:10 +00:00
use Utopia\Database\Document;
2024-02-12 01:18:19 +00:00
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
2023-05-29 16:32:33 +00:00
use Utopia\DSN\DSN;
2024-02-12 01:18:19 +00:00
use Utopia\Logger\Log;
use Utopia\Messaging\Adapter\Email as EmailAdapter;
use Utopia\Messaging\Adapter\Email\Mailgun;
2024-02-12 01:18:19 +00:00
use Utopia\Messaging\Adapter\Email\Sendgrid;
2024-03-06 17:34:21 +00:00
use Utopia\Messaging\Adapter\Email\SMTP;
use Utopia\Messaging\Adapter\Push\APNS;
2024-03-06 17:34:21 +00:00
use Utopia\Messaging\Adapter\Push as PushAdapter;
use Utopia\Messaging\Adapter\Push\FCM;
use Utopia\Messaging\Adapter\SMS as SMSAdapter;
2024-04-07 15:15:46 +00:00
use Utopia\Messaging\Adapter\SMS\Mock;
use Utopia\Messaging\Adapter\SMS\Msg91;
use Utopia\Messaging\Adapter\SMS\Telesign;
use Utopia\Messaging\Adapter\SMS\TextMagic;
2024-04-07 15:15:46 +00:00
use Utopia\Messaging\Adapter\SMS\Twilio;
use Utopia\Messaging\Adapter\SMS\Vonage;
use Utopia\Messaging\Messages\Email;
use Utopia\Messaging\Messages\Email\Attachment;
use Utopia\Messaging\Messages\Push;
use Utopia\Messaging\Messages\SMS;
2023-05-29 16:32:33 +00:00
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
2024-04-01 11:02:47 +00:00
use Utopia\System\System;
2023-10-04 10:45:59 +00:00
use function Swoole\Coroutine\batch;
2023-05-29 16:32:33 +00:00
class Messaging extends Action
{
private ?Local $localDevice = null;
2023-05-29 16:32:33 +00:00
public static function getName(): string
{
return 'messaging';
}
2023-06-02 03:54:34 +00:00
/**
2024-02-20 12:06:35 +00:00
* @throws \Exception
2023-06-02 03:54:34 +00:00
*/
2023-05-29 16:32:33 +00:00
public function __construct()
{
$this
->desc('Messaging worker')
->inject('message')
->inject('log')
->inject('dbForProject')
2024-02-20 14:10:51 +00:00
->inject('deviceForFiles')
2024-01-30 09:19:10 +00:00
->inject('queueForUsage')
->callback(fn (Message $message, Log $log, Database $dbForProject, Device $deviceForFiles, Usage $queueForUsage) => $this->action($message, $log, $dbForProject, $deviceForFiles, $queueForUsage));
2023-05-29 16:32:33 +00:00
}
2023-06-02 03:54:34 +00:00
/**
2023-10-01 17:39:26 +00:00
* @param Message $message
* @param Log $log
* @param Database $dbForProject
* @param Device $deviceForFiles
2024-01-30 09:19:10 +00:00
* @param Usage $queueForUsage
2023-10-01 17:39:26 +00:00
* @return void
2024-02-20 12:06:35 +00:00
* @throws \Exception
2023-06-02 03:54:34 +00:00
*/
public function action(
Message $message,
Log $log,
Database $dbForProject,
2024-02-20 14:10:51 +00:00
Device $deviceForFiles,
Usage $queueForUsage
): void {
Runtime::setHookFlags(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_TCP);
2023-05-29 16:32:33 +00:00
$payload = $message->getPayload() ?? [];
2024-01-30 11:29:15 +00:00
if (empty($payload)) {
2024-02-27 12:00:36 +00:00
throw new \Exception('Missing payload');
2024-01-30 11:29:15 +00:00
}
2024-02-20 12:06:35 +00:00
$type = $payload['type'] ?? '';
$project = new Document($payload['project'] ?? []);
2024-02-12 01:18:19 +00:00
2024-02-20 12:06:35 +00:00
switch ($type) {
case MESSAGE_SEND_TYPE_INTERNAL:
$message = new Document($payload['message'] ?? []);
$recipients = $payload['recipients'] ?? [];
$this->sendInternalSMSMessage($message, $project, $recipients, $queueForUsage, $log);
break;
case MESSAGE_SEND_TYPE_EXTERNAL:
$message = $dbForProject->getDocument('messages', $payload['messageId']);
$this->sendExternalMessage($dbForProject, $message, $deviceForFiles, $project);
2024-02-20 12:06:35 +00:00
break;
default:
2024-02-27 12:00:36 +00:00
throw new \Exception('Unknown message type: ' . $type);
2024-01-29 20:01:14 +00:00
}
}
2024-01-29 20:01:14 +00:00
2024-02-20 13:50:44 +00:00
private function sendExternalMessage(
Database $dbForProject,
Document $message,
2024-02-20 14:10:51 +00:00
Device $deviceForFiles,
Document $project,
2024-09-17 08:45:07 +00:00
Usage $queueForUsage
): void {
2024-01-11 02:55:08 +00:00
$topicIds = $message->getAttribute('topics', []);
$targetIds = $message->getAttribute('targets', []);
$userIds = $message->getAttribute('users', []);
$providerType = $message->getAttribute('providerType');
2024-01-30 09:19:10 +00:00
/**
* @var array<Document> $allTargets
2023-12-14 14:19:24 +00:00
*/
$allTargets = [];
2024-01-30 09:19:10 +00:00
2024-01-11 02:55:08 +00:00
if (\count($topicIds) > 0) {
$topics = $dbForProject->find('topics', [
Query::equal('$id', $topicIds),
2024-01-17 01:54:25 +00:00
Query::limit(\count($topicIds)),
2024-01-11 02:55:08 +00:00
]);
2023-10-30 18:07:57 +00:00
foreach ($topics as $topic) {
$targets = \array_filter($topic->getAttribute('targets'), function (Document $target) use ($providerType) {
return $target->getAttribute('providerType') === $providerType;
});
\array_push($allTargets, ...$targets);
2023-10-30 18:07:57 +00:00
}
2024-01-29 20:01:14 +00:00
}
2024-01-11 02:55:08 +00:00
if (\count($userIds) > 0) {
$users = $dbForProject->find('users', [
Query::equal('$id', $userIds),
2024-01-17 01:54:25 +00:00
Query::limit(\count($userIds)),
2024-01-11 02:55:08 +00:00
]);
2023-10-30 18:07:57 +00:00
foreach ($users as $user) {
$targets = \array_filter($user->getAttribute('targets'), function (Document $target) use ($providerType) {
return $target->getAttribute('providerType') === $providerType;
});
\array_push($allTargets, ...$targets);
2023-10-30 18:07:57 +00:00
}
}
2024-01-11 02:55:08 +00:00
if (\count($targetIds) > 0) {
$targets = $dbForProject->find('targets', [
Query::equal('$id', $targetIds),
Query::equal('providerType', [$providerType]),
2024-01-17 01:54:25 +00:00
Query::limit(\count($targetIds)),
2024-01-11 02:55:08 +00:00
]);
\array_push($allTargets, ...$targets);
2023-05-29 16:32:33 +00:00
}
if (empty($allTargets)) {
$dbForProject->updateDocument('messages', $message->getId(), $message->setAttributes([
2024-01-19 03:15:54 +00:00
'status' => MessageStatus::FAILED,
'deliveryErrors' => ['No valid recipients found.']
]));
Console::warning('No valid recipients found.');
2023-10-01 17:39:26 +00:00
return;
2023-05-29 16:32:33 +00:00
}
2024-02-27 11:56:52 +00:00
$default = $dbForProject->findOne('providers', [
2023-11-15 20:00:47 +00:00
Query::equal('enabled', [true]),
2024-02-27 11:56:52 +00:00
Query::equal('type', [$providerType]),
2023-11-14 12:44:07 +00:00
]);
2024-02-21 16:27:37 +00:00
2024-02-27 11:56:52 +00:00
if ($default === false || $default->isEmpty()) {
$dbForProject->updateDocument('messages', $message->getId(), $message->setAttributes([
2024-01-19 03:15:54 +00:00
'status' => MessageStatus::FAILED,
2024-02-27 11:56:52 +00:00
'deliveryErrors' => ['No enabled provider found.']
]));
2023-05-29 16:32:33 +00:00
2024-02-27 11:56:52 +00:00
Console::warning('No enabled provider found.');
2023-05-29 16:32:33 +00:00
return;
}
/**
2024-02-27 12:01:39 +00:00
* @var array<string, array<string, null>> $identifiers
2023-12-14 14:19:24 +00:00
*/
2024-01-11 02:55:08 +00:00
$identifiers = [];
/**
2024-02-27 11:56:52 +00:00
* @var array<Document> $providers
2023-12-14 14:19:24 +00:00
*/
$providers = [
2024-02-27 11:56:52 +00:00
$default->getId() => $default
];
2024-01-11 02:55:08 +00:00
2024-02-27 11:56:52 +00:00
foreach ($allTargets as $target) {
$providerId = $target->getAttribute('providerId');
2023-11-14 12:44:07 +00:00
2024-02-27 11:56:52 +00:00
if (!$providerId) {
$providerId = $default->getId();
2023-11-14 12:44:07 +00:00
}
if ($providerId) {
2024-01-11 02:55:08 +00:00
if (!\array_key_exists($providerId, $identifiers)) {
$identifiers[$providerId] = [];
}
2024-02-27 12:01:39 +00:00
// Use null as value to avoid duplicate keys
$identifiers[$providerId][$target->getAttribute('identifier')] = null;
2023-10-26 14:14:06 +00:00
}
}
/**
2024-01-11 02:55:08 +00:00
* @var array<array> $results
2023-12-14 14:19:24 +00:00
*/
2024-09-17 08:45:07 +00:00
$results = batch(\array_map(function ($providerId) use ($identifiers, &$providers, $default, $message, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
return function () use ($providerId, $identifiers, &$providers, $default, $message, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
if (\array_key_exists($providerId, $providers)) {
$provider = $providers[$providerId];
2023-11-14 12:44:07 +00:00
} else {
2024-01-11 02:55:08 +00:00
$provider = $dbForProject->getDocument('providers', $providerId);
2024-01-11 02:55:08 +00:00
if ($provider->isEmpty() || !$provider->getAttribute('enabled')) {
2024-02-27 12:00:36 +00:00
$provider = $default;
} else {
$providers[$providerId] = $provider;
}
2023-11-14 12:44:07 +00:00
}
2024-02-27 12:00:36 +00:00
$identifiersForProvider = $identifiers[$providerId];
2024-01-11 02:55:08 +00:00
2023-10-26 14:14:06 +00:00
$adapter = match ($provider->getAttribute('type')) {
2024-02-20 12:06:35 +00:00
MESSAGE_TYPE_SMS => $this->getSmsAdapter($provider),
MESSAGE_TYPE_PUSH => $this->getPushAdapter($provider),
MESSAGE_TYPE_EMAIL => $this->getEmailAdapter($provider),
2024-02-27 12:00:36 +00:00
default => throw new \Exception('Provider with the requested ID is of the incorrect type')
2023-10-04 10:45:59 +00:00
};
2023-11-14 12:44:07 +00:00
2024-02-27 11:59:40 +00:00
$batches = \array_chunk(
\array_keys($identifiersForProvider),
$adapter->getMaxMessagesPerRequest()
);
2023-10-26 14:14:06 +00:00
2024-09-17 08:45:07 +00:00
return batch(\array_map(function ($batch) use ($message, $provider, $adapter, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
return function () use ($batch, $message, $provider, $adapter, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
2023-10-30 18:07:57 +00:00
$deliveredTotal = 0;
2023-10-26 14:14:06 +00:00
$deliveryErrors = [];
$messageData = clone $message;
$messageData->setAttribute('to', $batch);
2023-11-14 12:44:07 +00:00
2023-10-26 14:14:06 +00:00
$data = match ($provider->getAttribute('type')) {
2024-02-20 12:06:35 +00:00
MESSAGE_TYPE_SMS => $this->buildSmsMessage($messageData, $provider),
2023-11-29 04:05:37 +00:00
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($dbForProject, $messageData, $provider, $deviceForFiles, $project),
2024-02-27 12:00:36 +00:00
default => throw new \Exception('Provider with the requested ID is of the incorrect type')
2023-10-26 14:14:06 +00:00
};
2023-11-14 12:44:07 +00:00
2023-10-26 14:14:06 +00:00
try {
2024-02-05 15:11:40 +00:00
$response = $adapter->send($data);
$deliveredTotal += $response['deliveredTo'];
foreach ($response['results'] as $result) {
if ($result['status'] === 'failure') {
$deliveryErrors[] = "Failed sending to target {$result['recipient']} with error: {$result['error']}";
}
// Deleting push targets when token has expired.
if (($result['error'] ?? '') === 'Expired device token') {
2024-01-11 02:55:08 +00:00
$target = $dbForProject->findOne('targets', [
2024-02-05 15:11:40 +00:00
Query::equal('identifier', [$result['recipient']])
2024-01-11 02:55:08 +00:00
]);
if ($target instanceof Document && !$target->isEmpty()) {
$dbForProject->updateDocument(
'targets',
$target->getId(),
$target->setAttribute('expired', true)
);
}
}
}
} catch (\Throwable $e) {
2024-02-27 11:59:40 +00:00
$deliveryErrors[] = 'Failed sending to targets with error: ' . $e->getMessage();
2023-10-26 14:14:06 +00:00
} finally {
2024-09-17 08:45:07 +00:00
$queueForUsage
->setProject($project)
2024-09-17 09:33:06 +00:00
->addMetric(METRIC_MESSAGES, ($deliveredTotal + $deliveryErrors))
2024-09-17 09:20:55 +00:00
->addMetric(METRIC_MESSAGES_SENT, $deliveredTotal)
->addMetric(METRIC_MESSAGES_FAILED, $deliveryErrors)
2024-09-17 09:33:06 +00:00
->addMetric(str_replace('{type}', $provider->getAttribute('type'), METRIC_MESSAGES_TYPE), ($deliveredTotal + $deliveryErrors))
2024-09-17 09:20:55 +00:00
->addMetric(str_replace('{type}', $provider->getAttribute('type'), METRIC_MESSAGES_TYPE_SENT), $deliveredTotal)
->addMetric(str_replace('{type}', $provider->getAttribute('type'), METRIC_MESSAGES_TYPE_FAILED), $deliveryErrors)
2024-09-17 09:33:06 +00:00
->addMetric(str_replace(['{type}', '{provider}'], [$provider->getAttribute('type'), $this->getSmsAdapter($provider)], METRIC_MESSAGES_TYPE_PROVIDER), ($deliveredTotal + $deliveryErrors))
2024-09-17 09:20:55 +00:00
->addMetric(str_replace(['{type}', '{provider}'], [$provider->getAttribute('type'), $this->getSmsAdapter($provider)], METRIC_MESSAGES_TYPE_PROVIDER_SENT), $deliveredTotal)
->addMetric(str_replace(['{type}', '{provider}'], [$provider->getAttribute('type'), $this->getSmsAdapter($provider)], METRIC_MESSAGES_TYPE_PROVIDER_FAILED), $deliveryErrors)
2024-09-17 08:45:07 +00:00
->trigger();
2023-10-26 14:14:06 +00:00
return [
2023-10-30 18:07:57 +00:00
'deliveredTotal' => $deliveredTotal,
2023-10-26 14:14:06 +00:00
'deliveryErrors' => $deliveryErrors,
];
}
};
}, $batches));
2023-10-04 10:45:59 +00:00
};
2024-01-11 02:55:08 +00:00
}, \array_keys($identifiers)));
2023-10-26 14:14:06 +00:00
2024-02-27 11:59:40 +00:00
$results = \array_merge(...$results);
2023-10-04 10:45:59 +00:00
2023-10-30 18:07:57 +00:00
$deliveredTotal = 0;
2023-10-04 10:45:59 +00:00
$deliveryErrors = [];
2023-11-14 12:44:07 +00:00
2023-10-04 10:45:59 +00:00
foreach ($results as $result) {
2023-10-30 18:07:57 +00:00
$deliveredTotal += $result['deliveredTotal'];
2023-10-04 10:45:59 +00:00
$deliveryErrors = \array_merge($deliveryErrors, $result['deliveryErrors']);
}
2023-11-14 12:44:07 +00:00
if (empty($deliveryErrors) && $deliveredTotal === 0) {
$deliveryErrors[] = 'Unknown error';
}
2023-10-06 13:53:46 +00:00
$message->setAttribute('deliveryErrors', $deliveryErrors);
2023-10-06 13:53:46 +00:00
if (\count($message->getAttribute('deliveryErrors')) > 0) {
2024-01-19 03:15:54 +00:00
$message->setAttribute('status', MessageStatus::FAILED);
} else {
2024-01-19 03:15:54 +00:00
$message->setAttribute('status', MessageStatus::SENT);
}
2023-11-14 12:44:07 +00:00
2024-09-17 08:45:07 +00:00
2023-10-30 18:07:57 +00:00
$message->removeAttribute('to');
foreach ($providers as $provider) {
$message->setAttribute('search', "{$message->getAttribute('search')} {$provider->getAttribute('name')} {$provider->getAttribute('provider')} {$provider->getAttribute('type')}");
}
2023-10-30 18:07:57 +00:00
$message->setAttribute('deliveredTotal', $deliveredTotal);
2023-10-06 13:53:46 +00:00
$message->setAttribute('deliveredAt', DateTime::now());
2023-10-20 11:32:13 +00:00
$dbForProject->updateDocument('messages', $message->getId(), $message);
2024-02-27 11:59:40 +00:00
// Delete any attachments that were downloaded to local storage
if ($provider->getAttribute('type') === MESSAGE_TYPE_EMAIL) {
2024-02-20 14:10:51 +00:00
if ($deviceForFiles->getType() === Storage::DEVICE_LOCAL) {
return;
}
$data = $message->getAttribute('data');
$attachments = $data['attachments'] ?? [];
2023-10-01 17:39:26 +00:00
foreach ($attachments as $attachment) {
$bucketId = $attachment['bucketId'];
$fileId = $attachment['fileId'];
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
2024-02-27 12:00:36 +00:00
throw new \Exception('Storage bucket with the requested ID could not be found');
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
2024-02-27 12:00:36 +00:00
throw new \Exception('Storage file with the requested ID could not be found');
}
$path = $file->getAttribute('path', '');
if ($this->getLocalDevice($project)->exists($path)) {
$this->getLocalDevice($project)->delete($path);
}
}
}
2022-06-08 13:57:34 +00:00
}
2024-02-20 12:06:35 +00:00
private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Usage $queueForUsage, Log $log): void
2023-11-15 20:00:47 +00:00
{
2024-04-01 11:02:47 +00:00
if (empty(System::getEnv('_APP_SMS_PROVIDER')) || empty(System::getEnv('_APP_SMS_FROM'))) {
throw new \Exception('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.');
2023-11-15 20:00:47 +00:00
}
2024-02-12 01:18:19 +00:00
if ($project->isEmpty()) {
2024-02-27 12:00:36 +00:00
throw new \Exception('Project not set in payload');
2024-02-12 01:18:19 +00:00
}
Console::log('Project: ' . $project->getId());
2024-04-01 11:02:47 +00:00
$denyList = System::getEnv('_APP_SMS_PROJECTS_DENY_LIST', '');
2024-02-12 01:18:19 +00:00
$denyList = explode(',', $denyList);
if (\in_array($project->getId(), $denyList)) {
Console::error('Project is in the deny list. Skipping...');
2023-05-29 16:32:33 +00:00
return;
}
2024-04-01 11:02:47 +00:00
$smsDSN = new DSN(System::getEnv('_APP_SMS_PROVIDER'));
2023-11-15 20:00:47 +00:00
$host = $smsDSN->getHost();
$password = $smsDSN->getPassword();
$user = $smsDSN->getUser();
$log->addTag('type', $host);
2024-04-01 11:02:47 +00:00
$from = System::getEnv('_APP_SMS_FROM');
2023-11-15 20:00:47 +00:00
$provider = new Document([
'$id' => ID::unique(),
'provider' => $host,
2023-11-29 04:05:37 +00:00
'type' => MESSAGE_TYPE_SMS,
2023-11-15 20:00:47 +00:00
'name' => 'Internal SMS',
'enabled' => true,
'credentials' => match ($host) {
'twilio' => [
'accountSid' => $user,
'authToken' => $password,
// Twilio Messaging Service SIDs always start with MG
// https://www.twilio.com/docs/messaging/services
'messagingServiceSid' => \str_starts_with($from, 'MG') ? $from : null
2023-11-15 20:00:47 +00:00
],
'textmagic' => [
'username' => $user,
'apiKey' => $password
],
'telesign' => [
2024-02-12 02:10:18 +00:00
'customerId' => $user,
'apiKey' => $password
2023-11-15 20:00:47 +00:00
],
'msg91' => [
'senderId' => $user,
2024-03-13 14:42:16 +00:00
'authKey' => $password,
'templateId' => $smsDSN->getParam('templateId', $from),
2023-11-15 20:00:47 +00:00
],
'vonage' => [
'apiKey' => $user,
'apiSecret' => $password
],
default => null
},
'options' => match ($host) {
'twilio' => [
'from' => \str_starts_with($from, 'MG') ? null : $from
],
default => [
'from' => $from
]
}
2023-11-15 20:00:47 +00:00
]);
2023-11-15 20:06:22 +00:00
2024-02-20 12:06:35 +00:00
$adapter = $this->getSmsAdapter($provider);
2023-11-15 20:00:47 +00:00
2024-02-27 11:59:40 +00:00
$batches = \array_chunk(
$recipients,
$adapter->getMaxMessagesPerRequest()
2023-05-29 16:32:33 +00:00
);
2024-02-27 11:59:40 +00:00
batch(\array_map(function ($batch) use ($message, $provider, $adapter, $project, $queueForUsage) {
return function () use ($batch, $message, $provider, $adapter, $project, $queueForUsage) {
2023-11-15 20:00:47 +00:00
$message->setAttribute('to', $batch);
2024-02-20 12:06:35 +00:00
$data = $this->buildSmsMessage($message, $provider);
2023-11-15 20:00:47 +00:00
try {
$adapter->send($data);
2024-05-12 16:51:16 +00:00
$countryCode = $adapter->getCountryCode($message['to'][0] ?? '');
2024-05-08 11:23:11 +00:00
if (!empty($countryCode)) {
$queueForUsage
2024-09-09 12:54:32 +00:00
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
2024-05-08 11:23:11 +00:00
}
2024-02-12 01:18:19 +00:00
$queueForUsage
2024-09-09 12:54:32 +00:00
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
2024-03-05 10:09:19 +00:00
->setProject($project)
2024-02-12 01:18:19 +00:00
->trigger();
2024-05-08 11:23:11 +00:00
} catch (\Throwable $th) {
throw new \Exception('Failed sending to targets with error: ' . $th->getMessage());
2023-11-15 20:00:47 +00:00
}
};
}, $batches));
}
2024-05-08 11:23:11 +00:00
2024-02-20 12:06:35 +00:00
private function getSmsAdapter(Document $provider): ?SMSAdapter
2023-10-04 10:45:59 +00:00
{
2023-10-06 13:53:46 +00:00
$credentials = $provider->getAttribute('credentials');
2024-02-12 01:18:19 +00:00
2023-10-06 13:53:46 +00:00
return match ($provider->getAttribute('provider')) {
2023-10-04 10:45:59 +00:00
'mock' => new Mock('username', 'password'),
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken'], null, isset($credentials['messagingServiceSid']) ? $credentials['messagingServiceSid'] : null),
'textmagic' => new TextMagic($credentials['username'], $credentials['apiKey']),
2024-02-12 02:10:18 +00:00
'telesign' => new Telesign($credentials['customerId'], $credentials['apiKey']),
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey'], $credentials['templateId']),
2023-10-04 10:45:59 +00:00
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
default => null
};
}
2024-02-20 12:06:35 +00:00
private function getPushAdapter(Document $provider): ?PushAdapter
2023-10-04 10:45:59 +00:00
{
2023-10-06 13:53:46 +00:00
$credentials = $provider->getAttribute('credentials');
2024-02-24 00:34:08 +00:00
$options = $provider->getAttribute('options');
2024-02-12 01:18:19 +00:00
2023-10-06 13:53:46 +00:00
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
2023-10-04 10:45:59 +00:00
'apns' => new APNS(
$credentials['authKey'],
$credentials['authKeyId'],
$credentials['teamId'],
$credentials['bundleId'],
2024-02-24 00:34:08 +00:00
$options['sandbox']
2023-10-04 10:45:59 +00:00
),
2024-02-05 15:13:56 +00:00
'fcm' => new FCM(\json_encode($credentials['serviceAccountJSON'])),
2023-10-04 10:45:59 +00:00
default => null
};
}
2024-02-20 12:06:35 +00:00
private function getEmailAdapter(Document $provider): ?EmailAdapter
2023-10-04 10:45:59 +00:00
{
2024-02-01 04:51:28 +00:00
$credentials = $provider->getAttribute('credentials', []);
$options = $provider->getAttribute('options', []);
2024-02-12 01:18:19 +00:00
2023-10-06 13:53:46 +00:00
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
2024-01-31 12:30:09 +00:00
'smtp' => new SMTP(
$credentials['host'],
$credentials['port'],
$credentials['username'],
$credentials['password'],
2024-02-01 04:51:28 +00:00
$options['encryption'],
$options['autoTLS'],
$options['mailer'],
2024-01-31 12:30:09 +00:00
),
'mailgun' => new Mailgun(
$credentials['apiKey'],
$credentials['domain'],
$credentials['isEuRegion']
),
'sendgrid' => new Sendgrid($credentials['apiKey']),
2023-10-04 10:45:59 +00:00
default => null
};
}
private function buildEmailMessage(
Database $dbForProject,
Document $message,
Document $provider,
2024-02-20 14:10:51 +00:00
Device $deviceForFiles,
Document $project,
): Email {
2024-01-31 12:30:09 +00:00
$fromName = $provider['options']['fromName'] ?? null;
$fromEmail = $provider['options']['fromEmail'] ?? null;
$replyToEmail = $provider['options']['replyToEmail'] ?? null;
$replyToName = $provider['options']['replyToName'] ?? null;
2023-12-14 14:19:24 +00:00
$data = $message['data'] ?? [];
$ccTargets = $data['cc'] ?? [];
$bccTargets = $data['bcc'] ?? [];
$cc = [];
$bcc = [];
$attachments = $data['attachments'] ?? [];
2023-12-14 14:19:24 +00:00
if (!empty($ccTargets)) {
$ccTargets = $dbForProject->find('targets', [
Query::equal('$id', $ccTargets),
Query::limit(\count($ccTargets)),
]);
2023-12-14 14:19:24 +00:00
foreach ($ccTargets as $ccTarget) {
$cc[] = ['email' => $ccTarget['identifier']];
}
}
if (!empty($bccTargets)) {
$bccTargets = $dbForProject->find('targets', [
Query::equal('$id', $bccTargets),
Query::limit(\count($bccTargets)),
]);
2023-12-14 14:19:24 +00:00
foreach ($bccTargets as $bccTarget) {
$bcc[] = ['email' => $bccTarget['identifier']];
}
}
if (!empty($attachments)) {
foreach ($attachments as &$attachment) {
$bucketId = $attachment['bucketId'];
$fileId = $attachment['fileId'];
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
2024-02-27 12:00:36 +00:00
throw new \Exception('Storage bucket with the requested ID could not be found');
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
2024-02-27 12:00:36 +00:00
throw new \Exception('Storage file with the requested ID could not be found');
}
$mimes = Config::getParam('storage-mimes');
$path = $file->getAttribute('path', '');
2024-02-20 14:10:51 +00:00
if (!$deviceForFiles->exists($path)) {
2024-02-27 12:00:36 +00:00
throw new \Exception('File not found in ' . $path);
}
$contentType = 'text/plain';
if (\in_array($file->getAttribute('mimeType'), $mimes)) {
$contentType = $file->getAttribute('mimeType');
}
2024-02-20 14:10:51 +00:00
if ($deviceForFiles->getType() !== Storage::DEVICE_LOCAL) {
$deviceForFiles->transfer($path, $path, $this->getLocalDevice($project));
}
$attachment = new Attachment(
$file->getAttribute('name'),
$path,
$contentType
);
}
}
$to = $message['to'];
2023-12-14 14:19:24 +00:00
$subject = $data['subject'];
$content = $data['content'];
2024-02-05 15:13:35 +00:00
$html = $data['html'] ?? false;
2023-10-04 10:45:59 +00:00
return new Email(
$to,
$subject,
$content,
$fromName,
$fromEmail,
$replyToName,
$replyToEmail,
$cc,
$bcc,
$attachments,
$html
);
}
2024-02-20 12:06:35 +00:00
private function buildSmsMessage(Document $message, Document $provider): SMS
{
$to = $message['to'];
$content = $message['data']['content'];
$from = $provider['options']['from'];
return new SMS(
$to,
$content,
$from
);
}
2023-10-04 10:45:59 +00:00
private function buildPushMessage(Document $message): Push
{
$to = $message['to'];
$title = $message['data']['title'];
$body = $message['data']['body'];
2024-02-05 15:13:35 +00:00
$data = $message['data']['data'] ?? null;
$action = $message['data']['action'] ?? null;
2024-03-07 17:40:45 +00:00
$image = $message['data']['image']['url'] ?? null;
2024-02-05 15:13:35 +00:00
$sound = $message['data']['sound'] ?? null;
$icon = $message['data']['icon'] ?? null;
$color = $message['data']['color'] ?? null;
$tag = $message['data']['tag'] ?? null;
$badge = $message['data']['badge'] ?? null;
2023-10-04 10:45:59 +00:00
return new Push(
$to,
$title,
$body,
$data,
$action,
$sound,
$image,
$icon,
$color,
$tag,
$badge
);
2023-05-29 16:32:33 +00:00
}
private function getLocalDevice($project): Local
{
2024-09-05 02:25:11 +00:00
if ($this->localDevice === null) {
$this->localDevice = new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}
return $this->localDevice;
}
2023-05-29 16:32:33 +00:00
}