1
0
Fork 0
mirror of synced 2024-06-03 11:24:48 +12:00

Merge pull request #7245 from appwrite/messaging-uniform-logic

Adds uniform error logic for messaging worker and extra params for email
This commit is contained in:
Jake Barnby 2024-01-09 13:53:40 +13:00 committed by GitHub
commit ecc254547d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 438 additions and 274 deletions

View file

@ -31,6 +31,7 @@ use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;
use MaxMind\Db\Reader;
@ -54,21 +55,28 @@ App::post('/v1/messaging/providers/mailgun')
->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Provider name.')
->param('from', '', new Email(), 'Sender email address.', true)
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('fromName', '', new Text(128), 'Sender Name.', true)
->param('fromEmail', '', new Email(), 'Sender email address.', true)
->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name. Reply to name must have reply to email as well.', true)
->param('replyToEmail', '', new Text(128), 'Email set in the reply to field for the mail. Default value is sender email. Reply to email must have reply to name as well.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, string $name, string $from, string $apiKey, string $domain, ?bool $isEuRegion, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $providerId, string $name, string $apiKey, string $domain, ?bool $isEuRegion, ?bool $enabled, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) {
$providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
$options = [];
$options = [
'fromName' => $fromName,
'fromEmail' => $fromEmail,
];
if (!empty($from)) {
$options ['from'] = $from;
if (!empty($replyToName) && !empty($replyToEmail)) {
$options['replyToName'] = $replyToName;
$options['replyToEmail'] = $replyToEmail;
}
$credentials = [];
@ -137,19 +145,26 @@ App::post('/v1/messaging/providers/sendgrid')
->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Provider name.')
->param('from', '', new Email(), 'Sender email address.', true)
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('fromName', '', new Text(128), 'Sender Name.', true)
->param('fromEmail', '', new Email(), 'Sender email address.', true)
->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name.', true)
->param('replyToEmail', '', new Text(128), 'Email set in the reply to field for the mail. Default value is sender email.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, string $name, string $from, string $apiKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $providerId, string $name, string $apiKey, ?bool $enabled, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) {
$providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
$options = [];
$options = [
'fromName' => $fromName,
'fromEmail' => $fromEmail,
];
if (!empty($from)) {
$options ['from'] = $from;
if (!empty($replyToName) && !empty($replyToEmail)) {
$options['replyToName'] = $replyToName;
$options['replyToEmail'] = $replyToEmail;
}
$credentials = [];
@ -221,7 +236,7 @@ App::post('/v1/messaging/providers/msg91')
$options = [];
if (!empty($from)) {
$options ['from'] = $from;
$options['from'] = $from;
}
$credentials = [];
@ -298,7 +313,7 @@ App::post('/v1/messaging/providers/telesign')
$options = [];
if (!empty($from)) {
$options ['from'] = $from;
$options['from'] = $from;
}
$credentials = [];
@ -375,7 +390,7 @@ App::post('/v1/messaging/providers/textmagic')
$options = [];
if (!empty($from)) {
$options ['from'] = $from;
$options['from'] = $from;
}
$credentials = [];
@ -452,7 +467,7 @@ App::post('/v1/messaging/providers/twilio')
$options = [];
if (!empty($from)) {
$options ['from'] = $from;
$options['from'] = $from;
}
$credentials = [];
@ -529,7 +544,7 @@ App::post('/v1/messaging/providers/vonage')
$options = [];
if (!empty($from)) {
$options ['from'] = $from;
$options['from'] = $from;
}
$credentials = [];
@ -593,21 +608,21 @@ App::post('/v1/messaging/providers/fcm')
->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Provider name.')
->param('serverKey', '', new Text(0), 'FCM server key.', true)
->param('serviceAccountJSON', null, new JSON(), 'FCM service account JSON.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, string $name, string $serverKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $providerId, string $name, ?array $serviceAccountJSON, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
$providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
$credentials = [];
if (!empty($serverKey)) {
$credentials['serverKey'] = $serverKey;
if (!\is_null($serviceAccountJSON)) {
$credentials['serviceAccountJSON'] = $serviceAccountJSON;
}
if ($enabled === true && \array_key_exists('serverKey', $credentials)) {
if ($enabled === true && \array_key_exists('serviceAccountJSON', $credentials)) {
$enabled = true;
} else {
$enabled = false;
@ -888,15 +903,18 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new UID(), 'Provider ID.')
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
->param('from', '', new Email(), 'Sender email address.', true)
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('fromName', '', new Text(128), 'Sender Name.', true)
->param('fromEmail', '', new Email(), 'Sender email address.', true)
->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name.', true)
->param('replyToEmail', '', new Text(128), 'Email set in the reply to field for the mail. Default value is sender email.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, string $name, ?bool $enabled, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $providerId, string $name, string $apiKey, string $domain, ?bool $isEuRegion, ?bool $enabled, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@ -912,12 +930,26 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
$provider->setAttribute('name', $name);
}
if (!empty($from)) {
$provider->setAttribute('options', [
'from' => $from,
]);
$options = $provider->getAttribute('options');
if (!empty($fromName)) {
$options['fromName'] = $fromName;
}
if (!empty($fromEmail)) {
$options['fromEmail'] = $fromEmail;
}
if (!empty($replyToName)) {
$options['replyToName'] = $replyToName;
}
if (!empty($replyToEmail)) {
$options['replyToEmail'] = $replyToEmail;
}
$provider->setAttribute('options', $options);
$credentials = $provider->getAttribute('credentials');
if ($isEuRegion === true || $isEuRegion === false) {
@ -976,11 +1008,14 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
->param('from', '', new Email(), 'Sender email address.', true)
->param('fromName', '', new Text(128), 'Sender Name.', true)
->param('fromEmail', '', new Email(), 'Sender email address.', true)
->param('replyToName', '', new Text(128), 'Name set in the Reply To field for the mail. Default value is Sender Name.', true)
->param('replyToEmail', '', new Text(128), 'Email set in the Reply To field for the mail. Default value is Sender Email.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@ -996,12 +1031,26 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
$provider->setAttribute('name', $name);
}
if (!empty($from)) {
$provider->setAttribute('options', [
'from' => $from,
]);
$options = $provider->getAttribute('options');
if (!empty($fromName)) {
$options['fromName'] = $fromName;
}
if (!empty($fromEmail)) {
$options['fromEmail'] = $fromEmail;
}
if (!empty($replyToName)) {
$options['replyToName'] = $replyToName;
}
if (!empty($replyToEmail)) {
$options['replyToEmail'] = $replyToEmail;
}
$provider->setAttribute('options', $options);
if (!empty($apiKey)) {
$provider->setAttribute('credentials', [
'apiKey' => $apiKey,
@ -1451,11 +1500,11 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
->param('providerId', '', new UID(), 'Provider ID.')
->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('serverKey', '', new Text(0), 'FCM Server Key.', true)
->param('serviceAccountJSON', null, new JSON(), 'FCM service account JSON.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
->action(function (string $providerId, string $name, ?bool $enabled, string $serverKey, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $providerId, string $name, ?bool $enabled, ?array $serviceAccountJSON, Event $queueForEvents, Database $dbForProject, Response $response) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@ -1471,12 +1520,12 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
$provider->setAttribute('name', $name);
}
if (!empty($serverKey)) {
$provider->setAttribute('credentials', ['serverKey' => $serverKey]);
if (!\is_null($serviceAccountJSON)) {
$provider->setAttribute('credentials', ['serviceAccountJSON' => $serviceAccountJSON]);
}
if ($enabled === true || $enabled === false) {
if ($enabled === true && \array_key_exists('serverKey', $provider->getAttribute('credentials'))) {
if ($enabled === true && \array_key_exists('serviceAccountJSON', $provider->getAttribute('credentials'))) {
$enabled = true;
} else {
$enabled = false;
@ -2227,9 +2276,11 @@ App::post('/v1/messaging/messages/email')
->param('messageId', '', new CustomId(), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('subject', '', new Text(998), 'Email Subject.')
->param('content', '', new Text(64230), 'Email Content.')
->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true)
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('cc', [], new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
->param('bcc', [], new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
->param('description', '', new Text(256), 'Description for message.', true)
->param('status', 'processing', new WhiteList(['draft', 'canceled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('html', false, new Boolean(), 'Is content of type HTML', true)
@ -2239,22 +2290,32 @@ App::post('/v1/messaging/messages/email')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, string $description, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()' ? ID::unique() : $messageId;
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, string $description, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
foreach ($targets as $target) {
$targetDocument = $dbForProject->getDocument('targets', $target);
$mergedTargets = \array_merge($targets, $cc, $bcc);
if ($targetDocument->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
if (!empty($mergedTargets)) {
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $mergedTargets),
Query::equal('providerType', [MESSAGE_TYPE_EMAIL]),
Query::limit(\count($mergedTargets)),
]);
if (\count($foundTargets) !== \count($mergedTargets)) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL);
}
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_EMAIL) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId());
foreach ($foundTargets as $target) {
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
}
}
@ -2269,6 +2330,8 @@ App::post('/v1/messaging/messages/email')
'subject' => $subject,
'content' => $content,
'html' => $html,
'cc' => $cc,
'bcc' => $bcc,
],
'status' => $status,
]));
@ -2304,9 +2367,9 @@ App::post('/v1/messaging/messages/sms')
->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new CustomId(), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('content', '', new Text(64230), 'SMS Content.')
->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true)
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true)
->param('status', 'processing', new WhiteList(['draft', 'canceled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
@ -2316,22 +2379,28 @@ App::post('/v1/messaging/messages/sms')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, string $content, array $topics, array $users, array $targets, string $description, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()' ? ID::unique() : $messageId;
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
foreach ($targets as $target) {
$targetDocument = $dbForProject->getDocument('targets', $target);
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
Query::equal('providerType', [MESSAGE_TYPE_SMS]),
Query::limit(\count($targets)),
]);
if ($targetDocument->isEmpty()) {
if (\count($foundTargets) !== \count($targets)) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS);
}
foreach ($foundTargets as $target) {
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_SMS) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS . ' ' . $targetDocument->getId());
}
}
$message = $dbForProject->createDocument('messages', new Document([
@ -2379,9 +2448,9 @@ App::post('/v1/messaging/messages/push')
->param('messageId', '', new CustomId(), 'Message ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('title', '', new Text(256), 'Title for push notification.')
->param('body', '', new Text(64230), 'Body for push notification.')
->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true)
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true)
->param('data', null, new JSON(), 'Additional Data for push notification.', true)
->param('action', '', new Text(256), 'Action for push notification.', true)
@ -2398,22 +2467,28 @@ App::post('/v1/messaging/messages/push')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, string $description, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()' ? ID::unique() : $messageId;
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
foreach ($targets as $target) {
$targetDocument = $dbForProject->getDocument('targets', $target);
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
Query::equal('providerType', [MESSAGE_TYPE_PUSH]),
Query::limit(\count($targets)),
]);
if ($targetDocument->isEmpty()) {
if (\count($foundTargets) !== \count($targets)) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH);
}
foreach ($foundTargets as $target) {
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH . ' ' . $targetDocument->getId());
}
}
$pushData = [];
@ -2619,21 +2694,23 @@ App::patch('/v1/messaging/messages/email/:messageId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true)
->param('subject', '', new Text(998), 'Email Subject.', true)
->param('description', '', new Text(256), 'Description for Message.', true)
->param('content', '', new Text(64230), 'Email Content.', true)
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('html', false, new Boolean(), 'Is content of type HTML', true)
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('subject', null, new Text(998), 'Email Subject.', true)
->param('description', null, new Text(256), 'Description for Message.', true)
->param('content', null, new Text(64230), 'Email Content.', true)
->param('status', null, new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('html', null, new Boolean(), 'Is content of type HTML', true)
->param('cc', null, new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
->param('bcc', null, new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $subject, string $description, string $content, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $description, ?string $content, ?string $status, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@ -2644,7 +2721,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
}
if (!is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) {
if (!\is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) {
throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED);
}
@ -2656,43 +2733,57 @@ App::patch('/v1/messaging/messages/email/:messageId')
$message->setAttribute('users', $users);
}
if (!\is_null($targets)) {
foreach ($targets as $target) {
$targetDocument = $dbForProject->getDocument('targets', $target);
if (!\is_null($targets) || !\is_null($cc) || !\is_null($bcc)) {
$mergedTargets = \array_merge(...\array_filter([$targets, $cc, $bcc]));
if ($targetDocument->isEmpty()) {
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $mergedTargets),
Query::equal('providerType', [MESSAGE_TYPE_EMAIL]),
Query::limit(\count($mergedTargets)),
]);
if (\count($foundTargets) !== \count($mergedTargets)) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL);
}
foreach ($foundTargets as $target) {
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_EMAIL) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId());
}
}
$message->setAttribute('targets', $targets);
}
$data = $message->getAttribute('data');
if (!empty($subject)) {
if (!\is_null($targets)) {
$message->setAttribute('targets', $targets);
}
if (!\is_null($subject)) {
$data['subject'] = $subject;
}
if (!empty($content)) {
if (!\is_null($content)) {
$data['content'] = $content;
}
if (!empty($html)) {
if (!\is_null($html)) {
$data['html'] = $html;
}
if (!\is_null($cc)) {
$data['cc'] = $cc;
}
if (!\is_null($bcc)) {
$data['bcc'] = $bcc;
}
$message->setAttribute('data', $data);
if (!empty($description)) {
if (!\is_null($description)) {
$message->setAttribute('description', $description);
}
if (!empty($status)) {
if (!\is_null($status)) {
$message->setAttribute('status', $status);
}
@ -2731,19 +2822,19 @@ App::patch('/v1/messaging/messages/sms/:messageId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true)
->param('content', '', new Text(64230), 'Email Content.', true)
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', null, new Text(256), 'Description for Message.', true)
->param('content', null, new Text(64230), 'Email Content.', true)
->param('status', null, new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $content, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $description, ?string $content, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@ -2767,16 +2858,20 @@ App::patch('/v1/messaging/messages/sms/:messageId')
}
if (!\is_null($targets)) {
foreach ($targets as $target) {
$targetDocument = $dbForProject->getDocument('targets', $target);
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
Query::equal('providerType', [MESSAGE_TYPE_SMS]),
Query::limit(\count($targets)),
]);
if ($targetDocument->isEmpty()) {
if (\count($foundTargets) !== \count($targets)) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS);
}
foreach ($foundTargets as $target) {
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_SMS) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS . ' ' . $targetDocument->getId());
}
}
$message->setAttribute('targets', $targets);
@ -2784,17 +2879,17 @@ App::patch('/v1/messaging/messages/sms/:messageId')
$data = $message->getAttribute('data');
if (!empty($content)) {
if (!\is_null($content)) {
$data['content'] = $content;
}
$message->setAttribute('data', $data);
if (!empty($status)) {
if (!\is_null($status)) {
$message->setAttribute('status', $status);
}
if (!empty($description)) {
if (!\is_null($description)) {
$message->setAttribute('description', $description);
}
@ -2833,27 +2928,27 @@ App::patch('/v1/messaging/messages/push/:messageId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true)
->param('title', '', new Text(256), 'Title for push notification.', true)
->param('body', '', new Text(64230), 'Body for push notification.', true)
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', null, new Text(256), 'Description for Message.', true)
->param('title', null, new Text(256), 'Title for push notification.', true)
->param('body', null, new Text(64230), 'Body for push notification.', true)
->param('data', null, new JSON(), 'Additional Data for push notification.', true)
->param('action', '', new Text(256), 'Action for push notification.', true)
->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true)
->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true)
->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true)
->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true)
->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true)
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
->param('action', null, new Text(256), 'Action for push notification.', true)
->param('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true)
->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true)
->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true)
->param('tag', null, new Text(256), 'Tag for push notification. Available only for Android platforms.', true)
->param('badge', null, new Integer(), 'Badge for push notification. Available only for iOS platforms.', true)
->param('status', null, new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft, cancelled, or processing.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents')
->inject('dbForProject')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $title, string $body, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $description, ?string $title, ?string $body, ?array $data, ?string $action, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@ -2877,16 +2972,20 @@ App::patch('/v1/messaging/messages/push/:messageId')
}
if (!\is_null($targets)) {
foreach ($targets as $target) {
$targetDocument = $dbForProject->getDocument('targets', $target);
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
Query::equal('providerType', [MESSAGE_TYPE_PUSH]),
Query::limit(\count($targets)),
]);
if ($targetDocument->isEmpty()) {
if (\count($foundTargets) !== \count($targets)) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH);
}
foreach ($foundTargets as $target) {
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH . ' ' . $targetDocument->getId());
}
}
$message->setAttribute('targets', $targets);
@ -2894,53 +2993,53 @@ App::patch('/v1/messaging/messages/push/:messageId')
$pushData = $message->getAttribute('data');
if ($title) {
if (!\is_null($title)) {
$pushData['title'] = $title;
}
if ($body) {
if (!\is_null($body)) {
$pushData['body'] = $body;
}
if (!is_null($data)) {
if (!\is_null($data)) {
$pushData['data'] = $data;
}
if ($action) {
if (!\is_null($action)) {
$pushData['action'] = $action;
}
if ($icon) {
if (!\is_null($icon)) {
$pushData['icon'] = $icon;
}
if ($sound) {
if (!\is_null($sound)) {
$pushData['sound'] = $sound;
}
if ($color) {
if (!\is_null($color)) {
$pushData['color'] = $color;
}
if ($tag) {
if (!\is_null($tag)) {
$pushData['tag'] = $tag;
}
if ($badge) {
if (!\is_null($badge)) {
$pushData['badge'] = $badge;
}
$message->setAttribute('data', $pushData);
if (!empty($status)) {
if (!\is_null($status)) {
$message->setAttribute('status', $status);
}
if (!empty($description)) {
if (!\is_null($description)) {
$message->setAttribute('description', $description);
}
if (!is_null($scheduledAt)) {
if (!\is_null($scheduledAt)) {
$message->setAttribute('scheduledAt', $scheduledAt);
}

View file

@ -277,7 +277,7 @@ $worker
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
Console::info("Worker $workerName started");
});
$worker->start();

View file

@ -56,7 +56,7 @@
"utopia-php/image": "0.5.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.2.*",
"utopia-php/messaging": "0.8.*",
"utopia-php/migration": "0.3.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.5.*",

20
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": "7041499af2e7b23795d8ef82c9d7a072",
"content-hash": "359b1e3bd27ac7362c6f8d145e64ae36",
"packages": [
{
"name": "adhocore/jwt",
@ -2270,26 +2270,28 @@
},
{
"name": "utopia-php/messaging",
"version": "0.2.0",
"version": "0.8.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43"
"reference": "64eca3faf02a79831f219d4f3ae05cd278a88b4b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/2d0f474a106bb1da285f85e105c29b46085d3a43",
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/64eca3faf02a79831f219d4f3ae05cd278a88b4b",
"reference": "64eca3faf02a79831f219d4f3ae05cd278a88b4b",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.0.0"
},
"require-dev": {
"laravel/pint": "^1.2",
"laravel/pint": "1.13.*",
"phpmailer/phpmailer": "6.8.*",
"phpunit/phpunit": "9.6.*"
"phpstan/phpstan": "1.10.*",
"phpunit/phpunit": "9.6.10"
},
"type": "library",
"autoload": {
@ -2312,9 +2314,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.2.0"
"source": "https://github.com/utopia-php/messaging/tree/0.8.0"
},
"time": "2023-09-14T20:48:42+00:00"
"time": "2023-12-15T06:44:08+00:00"
},
{
"name": "utopia-php/migration",

View file

@ -13,22 +13,23 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Messaging\Adapters\SMS as SMSAdapter;
use Utopia\Messaging\Adapters\SMS\Mock;
use Utopia\Messaging\Adapters\SMS\Msg91;
use Utopia\Messaging\Adapters\SMS\Telesign;
use Utopia\Messaging\Adapters\SMS\Textmagic;
use Utopia\Messaging\Adapters\SMS\Twilio;
use Utopia\Messaging\Adapters\SMS\Vonage;
use Utopia\Messaging\Adapters\Push as PushAdapter;
use Utopia\Messaging\Adapters\Push\APNS;
use Utopia\Messaging\Adapters\Push\FCM;
use Utopia\Messaging\Adapters\Email as EmailAdapter;
use Utopia\Messaging\Adapters\Email\Mailgun;
use Utopia\Messaging\Adapters\Email\SendGrid;
use Utopia\Messaging\Adapter\Email as EmailAdapter;
use Utopia\Messaging\Adapter\Email\Mailgun;
use Utopia\Messaging\Adapter\Email\Sendgrid;
use Utopia\Messaging\Adapter\Push as PushAdapter;
use Utopia\Messaging\Adapter\Push\APNS;
use Utopia\Messaging\Adapter\Push\FCM;
use Utopia\Messaging\Adapter\SMS as SMSAdapter;
use Utopia\Messaging\Adapter\SMS\Mock;
use Utopia\Messaging\Adapter\SMS\Msg91;
use Utopia\Messaging\Adapter\SMS\Telesign;
use Utopia\Messaging\Adapter\SMS\Textmagic;
use Utopia\Messaging\Adapter\SMS\Twilio;
use Utopia\Messaging\Adapter\SMS\Vonage;
use Utopia\Messaging\Messages\Email;
use Utopia\Messaging\Messages\Push;
use Utopia\Messaging\Messages\SMS;
use Utopia\Messaging\Response;
use function Swoole\Coroutine\batch;
@ -62,7 +63,7 @@ class Messaging extends Action
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
Console::error('Payload arg not found');
Console::error('Payload not found.');
return;
}
@ -84,14 +85,14 @@ class Messaging extends Action
$usersId = $message->getAttribute('users', []);
/**
* @var Document[] $recipients
*/
* @var Document[] $recipients
*/
$recipients = [];
if (\count($topicsId) > 0) {
$topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]);
foreach ($topics as $topic) {
$targets = \array_filter($topic->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
$targets = \array_filter($topic->getAttribute('targets'), fn(Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
$recipients = \array_merge($recipients, $targets);
}
}
@ -99,7 +100,7 @@ class Messaging extends Action
if (\count($usersId) > 0) {
$users = $dbForProject->find('users', [Query::equal('$id', $usersId)]);
foreach ($users as $user) {
$targets = \array_filter($user->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
$targets = \array_filter($user->getAttribute('targets'), fn(Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
$recipients = \array_merge($recipients, $targets);
}
}
@ -115,14 +116,16 @@ class Messaging extends Action
]);
/**
* @var array<string, array<string>> $identifiersByProviderId
*/
* @var array<string, array<string>> $identifiersByProviderId
*/
$identifiersByProviderId = [];
/**
* @var Document[] $providers
*/
$providers = [];
* @var Document[] $providers
*/
$providers = [
$primaryProvider->getId() => $primaryProvider
];
foreach ($recipients as $recipient) {
$providerId = $recipient->getAttribute('providerId');
@ -139,29 +142,28 @@ class Messaging extends Action
}
/**
* @var array[] $results
*/
* @var array[] $results
*/
$results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
return function () use ($providerId, $identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
$provider = new Document();
if ($primaryProvider->getId() === $providerId) {
$provider = $primaryProvider;
if (\array_key_exists($providerId, $providers)) {
$provider = $providers[$providerId];
} else {
$provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]);
if ($provider->isEmpty()) {
$provider = $primaryProvider;
} else {
$providers[$providerId] = $provider;
}
}
$providers[] = $provider;
$identifiers = $identifiersByProviderId[$providerId];
$adapter = match ($provider->getAttribute('type')) {
MESSAGE_TYPE_SMS => $this->sms($provider),
MESSAGE_TYPE_PUSH => $this->push($provider),
MESSAGE_TYPE_EMAIL => $this->email($provider),
MESSAGE_TYPE_EMAIL => $this->email($provider),
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
};
@ -169,8 +171,8 @@ class Messaging extends Action
$batches = \array_chunk($identifiers, $maxBatchSize);
$batchIndex = 0;
$results = batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
return batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex, $dbForProject) {
return function () use ($batch, $message, $provider, $adapter, $batchIndex, $dbForProject) {
$deliveredTotal = 0;
$deliveryErrors = [];
$messageData = clone $message;
@ -179,13 +181,29 @@ class Messaging extends Action
$data = match ($provider->getAttribute('type')) {
MESSAGE_TYPE_SMS => $this->buildSMSMessage($messageData, $provider),
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($messageData, $provider),
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($dbForProject, $messageData, $provider),
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
};
try {
$adapter->send($data);
$deliveredTotal += \count($batch);
$response = new Response($provider->getAttribute('type'));
$response->fromArray($adapter->send($data));
$deliveredTotal += $response->getDeliveredTo();
$details[] = $response->getDetails();
foreach ($details as $detail) {
if ($detail['status'] === 'failure') {
$deliveryErrors[] = "Failed sending to target {$detail['recipient']} with error: {$detail['error']}";
}
// Deleting push targets when token has expired.
if ($detail['error'] === 'Expired device token.') {
$target = $dbForProject->findOne('targets', [Query::equal('identifier', [$detail['recipient']])]);
if ($target instanceof Document && !$target->isEmpty()) {
$dbForProject->deleteDocument('targets', $target->getId());
}
}
}
} catch (\Exception $e) {
$deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage();
} finally {
@ -197,8 +215,6 @@ class Messaging extends Action
}
};
}, $batches));
return $results;
};
}, \array_keys($identifiersByProviderId)));
@ -313,7 +329,7 @@ class Messaging extends Action
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']),
'textmagic' => new Textmagic($credentials['username'], $credentials['apiKey']),
'telesign' => new Telesign($credentials['username'], $credentials['password']),
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']),
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey'], $credentials['templateId']),
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
default => null
};
@ -323,6 +339,7 @@ class Messaging extends Action
{
$credentials = $provider->getAttribute('credentials');
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
'apns' => new APNS(
$credentials['authKey'],
$credentials['authKeyId'],
@ -330,7 +347,7 @@ class Messaging extends Action
$credentials['bundleId'],
$credentials['endpoint']
),
'fcm' => new FCM($credentials['serverKey']),
'fcm' => new FCM($credentials['serviceAccountJSON']),
default => null
};
}
@ -339,21 +356,51 @@ class Messaging extends Action
{
$credentials = $provider->getAttribute('credentials');
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']),
'sendgrid' => new SendGrid($credentials['apiKey']),
'sendgrid' => new Sendgrid($credentials['apiKey']),
default => null
};
}
private function buildEmailMessage(Document $message, Document $provider): Email
private function buildEmailMessage(Database $dbForProject, Document $message, Document $provider): Email
{
$from = $provider['options']['from'];
$to = $message['to'];
$subject = $message['data']['subject'];
$content = $message['data']['content'];
$html = $message['data']['html'];
$fromName = $provider['options']['fromName'];
$fromEmail = $provider['options']['fromEmail'];
$replyToEmail = null;
$replyToName = null;
return new Email($to, $subject, $content, $from, null, $html);
if (isset($provider['options']['replyToName']) && isset($provider['options']['replyToEmail'])) {
$replyToName = $provider['options']['replyToName'];
$replyToEmail = $provider['options']['replyToEmail'];
}
$data = $message['data'] ?? [];
$ccTargets = $data['cc'] ?? [];
$bccTargets = $data['bcc'] ?? [];
$cc = [];
$bcc = [];
if (\count($ccTargets) > 0) {
$ccTargets = $dbForProject->find('targets', [Query::equal('identifier', $ccTargets)]);
foreach ($ccTargets as $ccTarget) {
$cc[] = ['email' => $ccTarget['identifier']];
}
}
if (\count($bccTargets) > 0) {
$bccTargets = $dbForProject->find('targets', [Query::equal('identifier', $bccTargets)]);
foreach ($bccTargets as $bccTarget) {
$bcc[] = ['email' => $bccTarget['identifier']];
}
}
$to = $message['to'];
$subject = $data['subject'];
$content = $data['content'];
$html = $data['html'];
return new Email($to, $subject, $content, $fromName, $fromEmail, $replyToName, $replyToEmail, $cc, $bcc, null, $html);
}
private function buildSMSMessage(Document $message, Document $provider): SMS

View file

@ -1790,8 +1790,8 @@ trait Base
}
}';
case self::$CREATE_MAILGUN_PROVIDER:
return 'mutation createMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $from: String!, $isEuRegion: Boolean!) {
messagingCreateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, from: $from, isEuRegion: $isEuRegion) {
return 'mutation createMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $fromName: String!, $fromEmail: String!, $isEuRegion: Boolean!, $replyToName: String, $replyToEmail: String) {
messagingCreateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, fromName: $fromName, fromEmail: $fromEmail, isEuRegion: $isEuRegion, replyToName: $replyToName, replyToEmail: $replyToEmail) {
_id
name
provider
@ -1800,8 +1800,8 @@ trait Base
}
}';
case self::$CREATE_SENDGRID_PROVIDER:
return 'mutation createSendgridProvider($providerId: String!, $name: String!, $from: String!, $apiKey: String!) {
messagingCreateSendgridProvider(providerId: $providerId, name: $name, from: $from, apiKey: $apiKey) {
return 'mutation createSendgridProvider($providerId: String!, $name: String!, $fromName: String!, $fromEmail: String!, $apiKey: String!, $replyToName: String, $replyToEmail: String) {
messagingCreateSendgridProvider(providerId: $providerId, name: $name, fromName: $fromName, fromEmail: $fromEmail, apiKey: $apiKey, replyToName: $replyToName, replyToEmail: $replyToEmail) {
_id
name
provider
@ -1860,8 +1860,8 @@ trait Base
}
}';
case self::$CREATE_FCM_PROVIDER:
return 'mutation createFcmProvider($providerId: String!, $name: String!, $serverKey: String!) {
messagingCreateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) {
return 'mutation createFcmProvider($providerId: String!, $name: String!, $serviceAccountJSON: Json) {
messagingCreateFcmProvider(providerId: $providerId, name: $name, serviceAccountJSON: $serviceAccountJSON) {
_id
name
provider
@ -1904,8 +1904,8 @@ trait Base
}
}';
case self::$UPDATE_MAILGUN_PROVIDER:
return 'mutation updateMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $isEuRegion: Boolean, $enabled: Boolean) {
messagingUpdateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, isEuRegion: $isEuRegion, enabled: $enabled) {
return 'mutation updateMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $isEuRegion: Boolean, $enabled: Boolean, $fromName: String, $fromEmail: String) {
messagingUpdateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, isEuRegion: $isEuRegion, enabled: $enabled, fromName: $fromName, fromEmail: $fromEmail) {
_id
name
provider
@ -1914,8 +1914,8 @@ trait Base
}
}';
case self::$UPDATE_SENDGRID_PROVIDER:
return 'mutation messagingUpdateSendgridProvider($providerId: String!, $name: String!, $apiKey: String!) {
messagingUpdateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey) {
return 'mutation messagingUpdateSendgridProvider($providerId: String!, $name: String!, $apiKey: String!, $enabled: Boolean, $fromName: String, $fromEmail: String) {
messagingUpdateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey, enabled: $enabled, fromName: $fromName, fromEmail: $fromEmail) {
_id
name
provider
@ -1974,8 +1974,8 @@ trait Base
}
}';
case self::$UPDATE_FCM_PROVIDER:
return 'mutation updateFcmProvider($providerId: String!, $name: String!, $serverKey: String!) {
messagingUpdateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) {
return 'mutation updateFcmProvider($providerId: String!, $name: String!, $serviceAccountJSON: Json) {
messagingUpdateFcmProvider(providerId: $providerId, name: $name, serviceAccountJSON: $serviceAccountJSON) {
_id
name
provider
@ -2098,8 +2098,8 @@ trait Base
}
}';
case self::$CREATE_EMAIL:
return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $cc: [String], $bcc: [String], $scheduledAt: String) {
messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, cc: $cc, bcc: $bcc, scheduledAt: $scheduledAt) {
_id
topics
users
@ -2178,8 +2178,8 @@ trait Base
}
}';
case self::$UPDATE_EMAIL:
return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $cc: [String], $bcc: [String], $scheduledAt: String) {
messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, cc: $cc, bcc: $bcc, scheduledAt: $scheduledAt) {
_id
topics
users

View file

@ -23,14 +23,16 @@ class MessagingTest extends Scope
'providerId' => ID::unique(),
'name' => 'Sengrid1',
'apiKey' => 'my-apikey',
'from' => 'sender-email@my-domain.com',
'fromName' => 'Sender Name',
'fromEmail' => 'sender-email@my-domain.com',
],
'Mailgun' => [
'providerId' => ID::unique(),
'name' => 'Mailgun1',
'apiKey' => 'my-apikey',
'domain' => 'my-domain',
'from' => 'sender-email@my-domain.com',
'fromName' => 'Sender Name',
'fromEmail' => 'sender-email@my-domain.com',
'isEuRegion' => false,
],
'Twilio' => [
@ -71,7 +73,12 @@ class MessagingTest extends Scope
'Fcm' => [
'providerId' => ID::unique(),
'name' => 'FCM1',
'serverKey' => 'my-serverkey',
'serviceAccountJSON' => [
'type' => 'service_account',
"project_id" => "test-project",
"private_key_id" => "test-private-key-id",
"private_key" => "test-private-key",
]
],
'Apns' => [
'providerId' => ID::unique(),
@ -97,6 +104,7 @@ class MessagingTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), $graphQLPayload);
\array_push($providers, $response['body']['data']['messagingCreate' . $key . 'Provider']);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($providersParams[$key]['name'], $response['body']['data']['messagingCreate' . $key . 'Provider']['name']);
@ -155,7 +163,12 @@ class MessagingTest extends Scope
'Fcm' => [
'providerId' => $providers[7]['_id'],
'name' => 'FCM2',
'serverKey' => 'my-serverkey',
'serviceAccountJSON' => [
'type' => 'service_account',
'project_id' => 'test-project',
'private_key_id' => 'test-project-id',
'private_key' => "test-private-key",
]
],
'Apns' => [
'providerId' => $providers[8]['_id'],
@ -374,7 +387,8 @@ class MessagingTest extends Scope
'providerId' => ID::unique(),
'name' => 'Sengrid1',
'apiKey' => 'my-apikey',
'from' => 'sender-email@my-domain.com',
'fromName' => 'Sender',
'fromEmail' => 'sender-email@my-domain.com',
]
];
$query = $this->getQuery(self::$CREATE_SENDGRID_PROVIDER);
@ -543,7 +557,8 @@ class MessagingTest extends Scope
$emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'));
$to = $emailDSN->getParam('to');
$from = $emailDSN->getParam('from');
$fromName = $emailDSN->getParam('fromName');
$fromEmail = $emailDSN->getParam('fromEmail');
$isEuRegion = $emailDSN->getParam('isEuRegion');
$apiKey = $emailDSN->getPassword();
$domain = $emailDSN->getUser();
@ -560,7 +575,8 @@ class MessagingTest extends Scope
'name' => 'Mailgun1',
'apiKey' => $apiKey,
'domain' => $domain,
'from' => $from,
'fromName' => $fromName,
'fromEmail' => $fromEmail,
'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN),
],
];
@ -956,9 +972,9 @@ class MessagingTest extends Scope
$pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'));
$to = $pushDSN->getParam('to');
$serverKey = $pushDSN->getPassword();
$serviceAccountJSON = $pushDSN->getParam('serviceAccountJSON');
if (empty($to) || empty($serverKey)) {
if (empty($to) || empty($serviceAccountJSON)) {
$this->markTestSkipped('Push provider not configured');
}
@ -968,7 +984,12 @@ class MessagingTest extends Scope
'variables' => [
'providerId' => ID::unique(),
'name' => 'FCM1',
'serverKey' => $serverKey,
'serviceAccountJSON' => [
'type' => 'service_account',
"project_id" => "test-project",
"private_key_id" => "test-private-key-id",
"private_key" => "test-private-key",
]
],
];
$provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([

View file

@ -60,7 +60,8 @@ class UsersTest extends Scope
'name' => 'Mailgun1',
'apiKey' => 'api-key',
'domain' => 'domain',
'from' => 'from@domain.com',
'fromName' => 'sender name',
'fromEmail' => 'from@domain.com',
'isEuRegion' => false,
],
];

View file

@ -24,7 +24,8 @@ trait MessagingBase
'name' => 'Mailgun1',
'apiKey' => 'my-apikey',
'domain' => 'my-domain',
'from' => 'sender-email@my-domain.com',
'fromName' => 'sender name',
'fromEmail' => 'sender-email@my-domain.com',
'isEuRegion' => false,
],
'twilio' => [
@ -65,7 +66,12 @@ trait MessagingBase
'fcm' => [
'providerId' => ID::unique(),
'name' => 'FCM1',
'serverKey' => 'my-serverkey',
'serviceAccountJSON' => [
'type' => 'service_account',
"project_id" => "test-project",
"private_key_id" => "test-private-key-id",
"private_key" => "test-private-key",
],
],
'apns' => [
'providerId' => ID::unique(),
@ -85,6 +91,7 @@ trait MessagingBase
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), $providersParams[$key]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals($providersParams[$key]['name'], $response['body']['name']);
\array_push($providers, $response['body']);
@ -135,7 +142,12 @@ trait MessagingBase
],
'fcm' => [
'name' => 'FCM2',
'serverKey' => 'my-serverkey',
'serviceAccountJSON' => [
'type' => 'service_account',
"project_id" => "test-project",
"private_key_id" => "test-private-key-id",
"private_key" => "test-private-key",
]
],
'apns' => [
'name' => 'APNS2',
@ -230,7 +242,6 @@ trait MessagingBase
], [
'topicId' => ID::unique(),
'name' => 'my-app',
'description' => 'web app'
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals('my-app', $response['body']['name']);
@ -579,17 +590,16 @@ trait MessagingBase
$emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'));
$to = $emailDSN->getParam('to');
$from = $emailDSN->getParam('from');
$isEuRegion = $emailDSN->getParam('isEuRegion');
$fromName = $emailDSN->getParam('fromName');
$fromEmail = $emailDSN->getParam('fromEmail');
$apiKey = $emailDSN->getPassword();
$domain = $emailDSN->getUser();
if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) {
if (empty($to) || empty($apiKey)) {
$this->markTestSkipped('Email provider not configured');
}
// Create provider
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/mailgun', \array_merge([
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@ -597,9 +607,8 @@ trait MessagingBase
'providerId' => ID::unique(),
'name' => 'Mailgun-provider',
'apiKey' => $apiKey,
'domain' => $domain,
'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN),
'from' => $from
'fromName' => $fromName,
'fromEmail' => $fromEmail
]);
$this->assertEquals(201, $provider['headers']['status-code']);
@ -631,27 +640,13 @@ trait MessagingBase
$this->assertEquals(201, $user['headers']['status-code']);
// Create Target
$target = $this->client->call(Client::METHOD_POST, '/users/' . $user['body']['$id'] . '/targets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'targetId' => ID::unique(),
'providerType' => 'email',
'providerId' => $provider['body']['$id'],
'identifier' => $to,
]);
$this->assertEquals(201, $target['headers']['status-code']);
// Create Subscriber
$subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'subscriberId' => ID::unique(),
'targetId' => $target['body']['$id'],
'targetId' => $user['body']['targets'][0]['$id'],
]);
$this->assertEquals(201, $subscriber['headers']['status-code']);
@ -664,13 +659,13 @@ trait MessagingBase
], [
'messageId' => ID::unique(),
'topics' => [$topic['body']['$id']],
'subject' => 'Khali beats Undertaker',
'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'subject' => 'New blog post',
'content' => 'Check out the new blog post at http://localhost',
]);
$this->assertEquals(201, $email['headers']['status-code']);
\sleep(5);
\sleep(2);
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'], [
'origin' => 'http://localhost',
@ -689,7 +684,7 @@ trait MessagingBase
/**
* @depends testSendEmail
*/
public function testUpdateEmail(array $email)
public function testUpdateEmail(array $email): void
{
$message = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/email/' . $email['body']['$id'], [
'content-type' => 'application/json',
@ -748,8 +743,8 @@ trait MessagingBase
$smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'));
$to = $smsDSN->getParam('to');
$from = $smsDSN->getParam('from');
$authKey = $smsDSN->getPassword();
$senderId = $smsDSN->getUser();
$authKey = $smsDSN->getPassword();
if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) {
$this->markTestSkipped('SMS provider not configured');
@ -762,7 +757,7 @@ trait MessagingBase
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'providerId' => ID::unique(),
'name' => 'Msg91-1',
'name' => 'Msg91Sender',
'senderId' => $senderId,
'authKey' => $authKey,
'from' => $from
@ -874,7 +869,7 @@ trait MessagingBase
'messageId' => ID::unique(),
'status' => 'draft',
'topics' => [$sms['body']['topics'][0]],
'content' => '047487',
'content' => 'Your OTP code is 123456',
]);
$this->assertEquals(201, $sms['headers']['status-code']);
@ -889,7 +884,7 @@ trait MessagingBase
$this->assertEquals(200, $sms['headers']['status-code']);
\sleep(5);
\sleep(2);
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $sms['body']['$id'], [
'origin' => 'http://localhost',
@ -909,11 +904,11 @@ trait MessagingBase
$this->markTestSkipped('Push DSN empty');
}
$pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'));
$to = $pushDSN->getParam('to');
$serverKey = $pushDSN->getPassword();
$dsn = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'));
$to = $dsn->getParam('to');
$serviceAccountJSON = $dsn->getParam('serviceAccountJSON');
if (empty($to) || empty($serverKey)) {
if (empty($to) || empty($serviceAccountJSON)) {
$this->markTestSkipped('Push provider not configured');
}
@ -925,7 +920,7 @@ trait MessagingBase
]), [
'providerId' => ID::unique(),
'name' => 'FCM-1',
'serverKey' => $serverKey,
'serviceAccountJSON' => $serviceAccountJSON,
]);
$this->assertEquals(201, $provider['headers']['status-code']);

View file

@ -1661,7 +1661,6 @@ class ProjectsConsoleClientTest extends Scope
foreach ($response['body'] as $key => $value) {
if (\preg_match($pattern, $key)) {
\var_dump('Matched key: ' . $key);
$matches[$key] = $value;
}
}