1
0
Fork 0
mirror of synced 2024-09-28 15:31:43 +12:00

Resolve merge conflicts

This commit is contained in:
Khushboo Verma 2024-01-10 13:13:09 +05:30
commit b74c7671e8
17 changed files with 535 additions and 301 deletions

View file

@ -75,8 +75,32 @@ jobs:
- name: Run Unit Tests - name: Run Unit Tests
run: docker compose exec appwrite test /usr/src/code/tests/unit run: docker compose exec appwrite test /usr/src/code/tests/unit
e2e_test: e2e_general_test:
name: E2E Test name: E2E General Test
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v3
- name: Load Cache
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Run General Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug
e2e_service_test:
name: E2E Service Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: setup needs: setup
strategy: strategy:
@ -120,4 +144,4 @@ jobs:
sleep 10 sleep 10
- name: Run ${{matrix.service}} Tests - name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug

View file

@ -876,7 +876,7 @@ App::get('/v1/account/identities')
}); });
App::delete('/v1/account/identities/:identityId') App::delete('/v1/account/identities/:identityId')
->desc('Delete Identity') ->desc('Delete identity')
->groups(['api', 'account']) ->groups(['api', 'account'])
->label('scope', 'account') ->label('scope', 'account')
->label('event', 'users.[userId].identities.[identityId].delete') ->label('event', 'users.[userId].identities.[identityId].delete')
@ -893,7 +893,8 @@ App::delete('/v1/account/identities/:identityId')
->param('identityId', '', new UID(), 'Identity ID.') ->param('identityId', '', new UID(), 'Identity ID.')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $identityId, Response $response, Database $dbForProject) { ->inject('queueForEvents')
->action(function (string $identityId, Response $response, Database $dbForProject, Event $queueForEvents) {
$identity = $dbForProject->getDocument('identities', $identityId); $identity = $dbForProject->getDocument('identities', $identityId);
@ -903,6 +904,11 @@ App::delete('/v1/account/identities/:identityId')
$dbForProject->deleteDocument('identities', $identityId); $dbForProject->deleteDocument('identities', $identityId);
$queueForEvents
->setParam('userId', $identity->getAttribute('userId'))
->setParam('identityId', $identity->getId())
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
return $response->noContent(); return $response->noContent();
}); });

View file

@ -31,6 +31,7 @@ use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale; use Utopia\Locale\Locale;
use Utopia\Validator\ArrayList; use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean; use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\JSON; use Utopia\Validator\JSON;
use Utopia\Validator\Text; use Utopia\Validator\Text;
use MaxMind\Db\Reader; use MaxMind\Db\Reader;
@ -54,21 +55,28 @@ App::post('/v1/messaging/providers/mailgun')
->label('sdk.response.model', Response::MODEL_PROVIDER) ->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('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('name', '', new Text(128), 'Provider name.')
->param('from', '', new Email(), 'Sender email address.', true)
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
->param('domain', '', new Text(0), 'Mailgun Domain.', true) ->param('domain', '', new Text(0), 'Mailgun Domain.', true)
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true) ->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', 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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('response') ->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; $providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
$options = []; $options = [
'fromName' => $fromName,
'fromEmail' => $fromEmail,
];
if (!empty($from)) { if (!empty($replyToName) && !empty($replyToEmail)) {
$options ['from'] = $from; $options['replyToName'] = $replyToName;
$options['replyToEmail'] = $replyToEmail;
} }
$credentials = []; $credentials = [];
@ -137,19 +145,26 @@ App::post('/v1/messaging/providers/sendgrid')
->label('sdk.response.model', Response::MODEL_PROVIDER) ->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('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('name', '', new Text(128), 'Provider name.')
->param('from', '', new Email(), 'Sender email address.', true)
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', 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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('response') ->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; $providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
$options = []; $options = [
'fromName' => $fromName,
'fromEmail' => $fromEmail,
];
if (!empty($from)) { if (!empty($replyToName) && !empty($replyToEmail)) {
$options ['from'] = $from; $options['replyToName'] = $replyToName;
$options['replyToEmail'] = $replyToEmail;
} }
$credentials = []; $credentials = [];
@ -221,7 +236,7 @@ App::post('/v1/messaging/providers/msg91')
$options = []; $options = [];
if (!empty($from)) { if (!empty($from)) {
$options ['from'] = $from; $options['from'] = $from;
} }
$credentials = []; $credentials = [];
@ -298,7 +313,7 @@ App::post('/v1/messaging/providers/telesign')
$options = []; $options = [];
if (!empty($from)) { if (!empty($from)) {
$options ['from'] = $from; $options['from'] = $from;
} }
$credentials = []; $credentials = [];
@ -375,7 +390,7 @@ App::post('/v1/messaging/providers/textmagic')
$options = []; $options = [];
if (!empty($from)) { if (!empty($from)) {
$options ['from'] = $from; $options['from'] = $from;
} }
$credentials = []; $credentials = [];
@ -452,7 +467,7 @@ App::post('/v1/messaging/providers/twilio')
$options = []; $options = [];
if (!empty($from)) { if (!empty($from)) {
$options ['from'] = $from; $options['from'] = $from;
} }
$credentials = []; $credentials = [];
@ -529,7 +544,7 @@ App::post('/v1/messaging/providers/vonage')
$options = []; $options = [];
if (!empty($from)) { if (!empty($from)) {
$options ['from'] = $from; $options['from'] = $from;
} }
$credentials = []; $credentials = [];
@ -593,21 +608,21 @@ App::post('/v1/messaging/providers/fcm')
->label('sdk.response.model', Response::MODEL_PROVIDER) ->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('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('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) ->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('response') ->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; $providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
$credentials = []; $credentials = [];
if (!empty($serverKey)) { if (!\is_null($serviceAccountJSON)) {
$credentials['serverKey'] = $serverKey; $credentials['serviceAccountJSON'] = $serviceAccountJSON;
} }
if ($enabled === true && \array_key_exists('serverKey', $credentials)) { if ($enabled === true && \array_key_exists('serviceAccountJSON', $credentials)) {
$enabled = true; $enabled = true;
} else { } else {
$enabled = false; $enabled = false;
@ -888,15 +903,18 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
->label('sdk.response.model', Response::MODEL_PROVIDER) ->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new UID(), 'Provider ID.') ->param('providerId', '', new UID(), 'Provider ID.')
->param('name', '', new Text(128), 'Provider name.', true) ->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('apiKey', '', new Text(0), 'Mailgun API Key.', true)
->param('domain', '', new Text(0), 'Mailgun Domain.', 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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('response') ->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); $provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) { if ($provider->isEmpty()) {
@ -912,12 +930,26 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
$provider->setAttribute('name', $name); $provider->setAttribute('name', $name);
} }
if (!empty($from)) { $options = $provider->getAttribute('options');
$provider->setAttribute('options', [
'from' => $from, 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'); $credentials = $provider->getAttribute('credentials');
if ($isEuRegion === true || $isEuRegion === false) { 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('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true)
->param('apiKey', '', new Text(0), 'Sendgrid API key.', 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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('response') ->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); $provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) { if ($provider->isEmpty()) {
@ -996,12 +1031,26 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
$provider->setAttribute('name', $name); $provider->setAttribute('name', $name);
} }
if (!empty($from)) { $options = $provider->getAttribute('options');
$provider->setAttribute('options', [
'from' => $from, 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)) { if (!empty($apiKey)) {
$provider->setAttribute('credentials', [ $provider->setAttribute('credentials', [
'apiKey' => $apiKey, 'apiKey' => $apiKey,
@ -1451,11 +1500,11 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
->param('providerId', '', new UID(), 'Provider ID.') ->param('providerId', '', new UID(), 'Provider ID.')
->param('name', '', new Text(128), 'Provider name.', true) ->param('name', '', new Text(128), 'Provider name.', true)
->param('enabled', null, new Boolean(), 'Set as enabled.', 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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('response') ->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); $provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) { if ($provider->isEmpty()) {
@ -1471,12 +1520,12 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
$provider->setAttribute('name', $name); $provider->setAttribute('name', $name);
} }
if (!empty($serverKey)) { if (!\is_null($serviceAccountJSON)) {
$provider->setAttribute('credentials', ['serverKey' => $serverKey]); $provider->setAttribute('credentials', ['serviceAccountJSON' => $serviceAccountJSON]);
} }
if ($enabled === true || $enabled === false) { 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; $enabled = true;
} else { } else {
$enabled = false; $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('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('subject', '', new Text(998), 'Email Subject.')
->param('content', '', new Text(64230), 'Email Content.') ->param('content', '', new Text(64230), 'Email Content.')
->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true) ->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true) ->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets 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('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('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) ->param('html', false, new Boolean(), 'Is content of type HTML', true)
@ -2239,22 +2290,32 @@ App::post('/v1/messaging/messages/email')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->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) { ->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; $messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
throw new Exception(Exception::MESSAGE_MISSING_TARGET); throw new Exception(Exception::MESSAGE_MISSING_TARGET);
} }
foreach ($targets as $target) { $mergedTargets = \array_merge($targets, $cc, $bcc);
$targetDocument = $dbForProject->getDocument('targets', $target);
if ($targetDocument->isEmpty()) { if (!empty($mergedTargets)) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND); $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) { foreach ($foundTargets as $target) {
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId()); if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
} }
} }
@ -2269,6 +2330,8 @@ App::post('/v1/messaging/messages/email')
'subject' => $subject, 'subject' => $subject,
'content' => $content, 'content' => $content,
'html' => $html, 'html' => $html,
'cc' => $cc,
'bcc' => $bcc,
], ],
'status' => $status, 'status' => $status,
])); ]));
@ -2304,9 +2367,9 @@ App::post('/v1/messaging/messages/sms')
->label('sdk.response.model', Response::MODEL_MESSAGE) ->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('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('content', '', new Text(64230), 'SMS Content.')
->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true) ->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true) ->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true) ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', 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('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) ->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('queueForMessaging')
->inject('response') ->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) { ->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) { if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
throw new Exception(Exception::MESSAGE_MISSING_TARGET); throw new Exception(Exception::MESSAGE_MISSING_TARGET);
} }
foreach ($targets as $target) { $foundTargets = $dbForProject->find('targets', [
$targetDocument = $dbForProject->getDocument('targets', $target); 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); 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([ $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('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('title', '', new Text(256), 'Title for push notification.')
->param('body', '', new Text(64230), 'Body 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('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true) ->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true) ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true) ->param('description', '', new Text(256), 'Description for Message.', true)
->param('data', null, new JSON(), 'Additional Data 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('action', '', new Text(256), 'Action for push notification.', true)
@ -2398,22 +2467,28 @@ App::post('/v1/messaging/messages/push')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->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) { ->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) { if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
throw new Exception(Exception::MESSAGE_MISSING_TARGET); throw new Exception(Exception::MESSAGE_MISSING_TARGET);
} }
foreach ($targets as $target) { $foundTargets = $dbForProject->find('targets', [
$targetDocument = $dbForProject->getDocument('targets', $target); 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); 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 = []; $pushData = [];
@ -2619,21 +2694,23 @@ App::patch('/v1/messaging/messages/email/:messageId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE) ->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new UID(), 'Message ID.') ->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true) ->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true) ->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true) ->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('subject', '', new Text(998), 'Email Subject.', true) ->param('subject', null, new Text(998), 'Email Subject.', true)
->param('description', '', new Text(256), 'Description for Message.', true) ->param('description', null, new Text(256), 'Description for Message.', true)
->param('content', '', new Text(64230), 'Email Content.', true) ->param('content', null, 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('status', null, 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('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) ->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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->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); $message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) { if ($message->isEmpty()) {
@ -2644,7 +2721,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
throw new Exception(Exception::MESSAGE_ALREADY_SENT); 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); throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED);
} }
@ -2656,43 +2733,57 @@ App::patch('/v1/messaging/messages/email/:messageId')
$message->setAttribute('users', $users); $message->setAttribute('users', $users);
} }
if (!\is_null($targets)) { if (!\is_null($targets) || !\is_null($cc) || !\is_null($bcc)) {
foreach ($targets as $target) { $mergedTargets = \array_merge(...\array_filter([$targets, $cc, $bcc]));
$targetDocument = $dbForProject->getDocument('targets', $target);
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); 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'); $data = $message->getAttribute('data');
if (!empty($subject)) { if (!\is_null($targets)) {
$message->setAttribute('targets', $targets);
}
if (!\is_null($subject)) {
$data['subject'] = $subject; $data['subject'] = $subject;
} }
if (!empty($content)) { if (!\is_null($content)) {
$data['content'] = $content; $data['content'] = $content;
} }
if (!empty($html)) { if (!\is_null($html)) {
$data['html'] = $html; $data['html'] = $html;
} }
if (!\is_null($cc)) {
$data['cc'] = $cc;
}
if (!\is_null($bcc)) {
$data['bcc'] = $bcc;
}
$message->setAttribute('data', $data); $message->setAttribute('data', $data);
if (!empty($description)) { if (!\is_null($description)) {
$message->setAttribute('description', $description); $message->setAttribute('description', $description);
} }
if (!empty($status)) { if (!\is_null($status)) {
$message->setAttribute('status', $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.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE) ->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new UID(), 'Message ID.') ->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) ->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) ->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true) ->param('description', null, new Text(256), 'Description for Message.', true)
->param('content', '', new Text(64230), 'Email Content.', true) ->param('content', null, 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('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) ->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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->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); $message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) { if ($message->isEmpty()) {
@ -2767,16 +2858,20 @@ App::patch('/v1/messaging/messages/sms/:messageId')
} }
if (!\is_null($targets)) { if (!\is_null($targets)) {
foreach ($targets as $target) { $foundTargets = $dbForProject->find('targets', [
$targetDocument = $dbForProject->getDocument('targets', $target); 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); 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); $message->setAttribute('targets', $targets);
@ -2784,17 +2879,17 @@ App::patch('/v1/messaging/messages/sms/:messageId')
$data = $message->getAttribute('data'); $data = $message->getAttribute('data');
if (!empty($content)) { if (!\is_null($content)) {
$data['content'] = $content; $data['content'] = $content;
} }
$message->setAttribute('data', $data); $message->setAttribute('data', $data);
if (!empty($status)) { if (!\is_null($status)) {
$message->setAttribute('status', $status); $message->setAttribute('status', $status);
} }
if (!empty($description)) { if (!\is_null($description)) {
$message->setAttribute('description', $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.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE) ->label('sdk.response.model', Response::MODEL_MESSAGE)
->param('messageId', '', new UID(), 'Message ID.') ->param('messageId', '', new UID(), 'Message ID.')
->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) ->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) ->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('description', '', new Text(256), 'Description for Message.', true) ->param('description', null, new Text(256), 'Description for Message.', true)
->param('title', '', new Text(256), 'Title for push notification.', true) ->param('title', null, new Text(256), 'Title for push notification.', true)
->param('body', '', new Text(64230), 'Body 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('data', null, new JSON(), 'Additional Data for push notification.', true)
->param('action', '', new Text(256), 'Action for push notification.', true) ->param('action', null, 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('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true)
->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true) ->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true)
->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) ->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true)
->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('tag', null, new Text(256), 'Tag for push notification. Available only for Android platforms.', true)
->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('badge', null, new Integer(), 'Badge for push notification. Available only for iOS platforms.', true)
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', 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) ->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('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->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); $message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) { if ($message->isEmpty()) {
@ -2877,16 +2972,20 @@ App::patch('/v1/messaging/messages/push/:messageId')
} }
if (!\is_null($targets)) { if (!\is_null($targets)) {
foreach ($targets as $target) { $foundTargets = $dbForProject->find('targets', [
$targetDocument = $dbForProject->getDocument('targets', $target); 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); 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); $message->setAttribute('targets', $targets);
@ -2894,53 +2993,53 @@ App::patch('/v1/messaging/messages/push/:messageId')
$pushData = $message->getAttribute('data'); $pushData = $message->getAttribute('data');
if ($title) { if (!\is_null($title)) {
$pushData['title'] = $title; $pushData['title'] = $title;
} }
if ($body) { if (!\is_null($body)) {
$pushData['body'] = $body; $pushData['body'] = $body;
} }
if (!is_null($data)) { if (!\is_null($data)) {
$pushData['data'] = $data; $pushData['data'] = $data;
} }
if ($action) { if (!\is_null($action)) {
$pushData['action'] = $action; $pushData['action'] = $action;
} }
if ($icon) { if (!\is_null($icon)) {
$pushData['icon'] = $icon; $pushData['icon'] = $icon;
} }
if ($sound) { if (!\is_null($sound)) {
$pushData['sound'] = $sound; $pushData['sound'] = $sound;
} }
if ($color) { if (!\is_null($color)) {
$pushData['color'] = $color; $pushData['color'] = $color;
} }
if ($tag) { if (!\is_null($tag)) {
$pushData['tag'] = $tag; $pushData['tag'] = $tag;
} }
if ($badge) { if (!\is_null($badge)) {
$pushData['badge'] = $badge; $pushData['badge'] = $badge;
} }
$message->setAttribute('data', $pushData); $message->setAttribute('data', $pushData);
if (!empty($status)) { if (!\is_null($status)) {
$message->setAttribute('status', $status); $message->setAttribute('status', $status);
} }
if (!empty($description)) { if (!\is_null($description)) {
$message->setAttribute('description', $description); $message->setAttribute('description', $description);
} }
if (!is_null($scheduledAt)) { if (!\is_null($scheduledAt)) {
$message->setAttribute('scheduledAt', $scheduledAt); $message->setAttribute('scheduledAt', $scheduledAt);
} }

View file

@ -1598,7 +1598,7 @@ App::delete('/v1/users/:userId/targets/:targetId')
}); });
App::delete('/v1/users/identities/:identityId') App::delete('/v1/users/identities/:identityId')
->desc('Delete Identity') ->desc('Delete identity')
->groups(['api', 'users']) ->groups(['api', 'users'])
->label('event', 'users.[userId].identities.[identityId].delete') ->label('event', 'users.[userId].identities.[identityId].delete')
->label('scope', 'users.write') ->label('scope', 'users.write')
@ -1614,7 +1614,8 @@ App::delete('/v1/users/identities/:identityId')
->param('identityId', '', new UID(), 'Identity ID.') ->param('identityId', '', new UID(), 'Identity ID.')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $identityId, Response $response, Database $dbForProject) { ->inject('queueForEvents')
->action(function (string $identityId, Response $response, Database $dbForProject, Event $queueForEvents) {
$identity = $dbForProject->getDocument('identities', $identityId); $identity = $dbForProject->getDocument('identities', $identityId);
@ -1624,6 +1625,11 @@ App::delete('/v1/users/identities/:identityId')
$dbForProject->deleteDocument('identities', $identityId); $dbForProject->deleteDocument('identities', $identityId);
$queueForEvents
->setParam('userId', $identity->getAttribute('userId'))
->setParam('identityId', $identity->getId())
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
return $response->noContent(); return $response->noContent();
}); });

View file

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

View file

@ -56,7 +56,7 @@
"utopia-php/image": "0.5.*", "utopia-php/image": "0.5.*",
"utopia-php/locale": "0.4.*", "utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*", "utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.2.*", "utopia-php/messaging": "0.8.*",
"utopia-php/migration": "0.3.*", "utopia-php/migration": "0.3.*",
"utopia-php/orchestration": "0.9.*", "utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.5.*", "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", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "92b527993518d6b893e3d226064edac0", "content-hash": "359b1e3bd27ac7362c6f8d145e64ae36",
"packages": [ "packages": [
{ {
"name": "adhocore/jwt", "name": "adhocore/jwt",
@ -2272,26 +2272,28 @@
}, },
{ {
"name": "utopia-php/messaging", "name": "utopia-php/messaging",
"version": "0.2.0", "version": "0.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/messaging.git", "url": "https://github.com/utopia-php/messaging.git",
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43" "reference": "64eca3faf02a79831f219d4f3ae05cd278a88b4b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/2d0f474a106bb1da285f85e105c29b46085d3a43", "url": "https://api.github.com/repos/utopia-php/messaging/zipball/64eca3faf02a79831f219d4f3ae05cd278a88b4b",
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43", "reference": "64eca3faf02a79831f219d4f3ae05cd278a88b4b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-curl": "*", "ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.0.0" "php": ">=8.0.0"
}, },
"require-dev": { "require-dev": {
"laravel/pint": "^1.2", "laravel/pint": "1.13.*",
"phpmailer/phpmailer": "6.8.*", "phpmailer/phpmailer": "6.8.*",
"phpunit/phpunit": "9.6.*" "phpstan/phpstan": "1.10.*",
"phpunit/phpunit": "9.6.10"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -2314,9 +2316,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/messaging/issues", "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", "name": "utopia-php/migration",

View file

@ -8,6 +8,7 @@ use Appwrite\Docker\Env;
use Appwrite\Utopia\View; use Appwrite\Utopia\View;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Config\Config; use Utopia\Config\Config;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text; use Utopia\Validator\Text;
use Utopia\Platform\Action; use Utopia\Platform\Action;
@ -24,15 +25,16 @@ class Install extends Action
{ {
$this $this
->desc('Install Appwrite') ->desc('Install Appwrite')
->param('httpPort', '', new Text(4), 'Server HTTP port', true) ->param('http-port', '', new Text(4), 'Server HTTP port', true)
->param('httpsPort', '', new Text(4), 'Server HTTPS port', true) ->param('https-port', '', new Text(4), 'Server HTTPS port', true)
->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true) ->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true)
->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true) ->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true)
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true) ->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
->callback(fn ($httpPort, $httpsPort, $organization, $image, $interactive) => $this->action($httpPort, $httpsPort, $organization, $image, $interactive)); ->param('no-start', false, new Boolean(true), 'Run an interactive session', true)
->callback(fn ($httpPort, $httpsPort, $organization, $image, $interactive, $noStart) => $this->action($httpPort, $httpsPort, $organization, $image, $interactive, $noStart));
} }
public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive): void public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive, bool $noStart): void
{ {
$config = Config::getParam('variables'); $config = Config::getParam('variables');
$defaultHTTPPort = '80'; $defaultHTTPPort = '80';
@ -220,9 +222,11 @@ class Install extends Action
} }
} }
Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\""); $exit = 0;
if (!$noStart) {
$exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr); Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\"");
$exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
}
if ($exit !== 0) { if ($exit !== 0) {
$message = 'Failed to install Appwrite dockers'; $message = 'Failed to install Appwrite dockers';

View file

@ -3,6 +3,7 @@
namespace Appwrite\Platform\Tasks; namespace Appwrite\Platform\Tasks;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text; use Utopia\Validator\Text;
class Upgrade extends Install class Upgrade extends Install
@ -16,15 +17,16 @@ class Upgrade extends Install
{ {
$this $this
->desc('Upgrade Appwrite') ->desc('Upgrade Appwrite')
->param('httpPort', '', new Text(4), 'Server HTTP port', true) ->param('http-port', '', new Text(4), 'Server HTTP port', true)
->param('httpsPort', '', new Text(4), 'Server HTTPS port', true) ->param('https-port', '', new Text(4), 'Server HTTPS port', true)
->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true) ->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true)
->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true) ->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true)
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true) ->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
->callback(fn ($httpPort, $httpsPort, $organization, $image, $interactive) => $this->action($httpPort, $httpsPort, $organization, $image, $interactive)); ->param('no-start', false, new Boolean(true), 'Run an interactive session', true)
->callback(fn ($httpPort, $httpsPort, $organization, $image, $interactive, $noStart) => $this->action($httpPort, $httpsPort, $organization, $image, $interactive, $noStart));
} }
public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive): void public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive, bool $noStart): void
{ {
// Check for previous installation // Check for previous installation
$data = @file_get_contents($this->path . '/docker-compose.yml'); $data = @file_get_contents($this->path . '/docker-compose.yml');
@ -37,6 +39,6 @@ class Upgrade extends Install
Console::log(' └── docker-compose.yml'); Console::log(' └── docker-compose.yml');
Console::exit(1); Console::exit(1);
} }
parent::action($httpPort, $httpsPort, $organization, $image, $interactive); parent::action($httpPort, $httpsPort, $organization, $image, $interactive, $noStart);
} }
} }

View file

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

View file

@ -7,6 +7,7 @@ class Topics extends Base
public const ALLOWED_ATTRIBUTES = [ public const ALLOWED_ATTRIBUTES = [
'name', 'name',
'description', 'description',
'total'
]; ];
/** /**

View file

@ -72,6 +72,7 @@ class Executor
) { ) {
$runtimeId = "$projectId-$deploymentId-build"; $runtimeId = "$projectId-$deploymentId-build";
$route = "/runtimes"; $route = "/runtimes";
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
$params = [ $params = [
'runtimeId' => $runtimeId, 'runtimeId' => $runtimeId,
'source' => $source, 'source' => $source,
@ -84,10 +85,9 @@ class Executor
'cpus' => $this->cpus, 'cpus' => $this->cpus,
'memory' => $this->memory, 'memory' => $this->memory,
'version' => $version, 'version' => $version,
'timeout' => $timeout,
]; ];
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); $response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$status = $response['headers']['status-code']; $status = $response['headers']['status-code'];
@ -111,7 +111,7 @@ class Executor
string $projectId, string $projectId,
callable $callback callable $callback
) { ) {
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900); $timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
$runtimeId = "$projectId-$deploymentId-build"; $runtimeId = "$projectId-$deploymentId-build";
$route = "/runtimes/{$runtimeId}/logs"; $route = "/runtimes/{$runtimeId}/logs";

View file

@ -1790,8 +1790,8 @@ trait Base
} }
}'; }';
case self::$CREATE_MAILGUN_PROVIDER: case self::$CREATE_MAILGUN_PROVIDER:
return 'mutation createMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $from: String!, $isEuRegion: Boolean!) { 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, from: $from, isEuRegion: $isEuRegion) { messagingCreateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, fromName: $fromName, fromEmail: $fromEmail, isEuRegion: $isEuRegion, replyToName: $replyToName, replyToEmail: $replyToEmail) {
_id _id
name name
provider provider
@ -1800,8 +1800,8 @@ trait Base
} }
}'; }';
case self::$CREATE_SENDGRID_PROVIDER: case self::$CREATE_SENDGRID_PROVIDER:
return 'mutation createSendgridProvider($providerId: String!, $name: String!, $from: String!, $apiKey: String!) { return 'mutation createSendgridProvider($providerId: String!, $name: String!, $fromName: String!, $fromEmail: String!, $apiKey: String!, $replyToName: String, $replyToEmail: String) {
messagingCreateSendgridProvider(providerId: $providerId, name: $name, from: $from, apiKey: $apiKey) { messagingCreateSendgridProvider(providerId: $providerId, name: $name, fromName: $fromName, fromEmail: $fromEmail, apiKey: $apiKey, replyToName: $replyToName, replyToEmail: $replyToEmail) {
_id _id
name name
provider provider
@ -1860,8 +1860,8 @@ trait Base
} }
}'; }';
case self::$CREATE_FCM_PROVIDER: case self::$CREATE_FCM_PROVIDER:
return 'mutation createFcmProvider($providerId: String!, $name: String!, $serverKey: String!) { return 'mutation createFcmProvider($providerId: String!, $name: String!, $serviceAccountJSON: Json) {
messagingCreateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) { messagingCreateFcmProvider(providerId: $providerId, name: $name, serviceAccountJSON: $serviceAccountJSON) {
_id _id
name name
provider provider
@ -1904,8 +1904,8 @@ trait Base
} }
}'; }';
case self::$UPDATE_MAILGUN_PROVIDER: case self::$UPDATE_MAILGUN_PROVIDER:
return 'mutation updateMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $isEuRegion: Boolean, $enabled: Boolean) { 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) { messagingUpdateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, isEuRegion: $isEuRegion, enabled: $enabled, fromName: $fromName, fromEmail: $fromEmail) {
_id _id
name name
provider provider
@ -1914,8 +1914,8 @@ trait Base
} }
}'; }';
case self::$UPDATE_SENDGRID_PROVIDER: case self::$UPDATE_SENDGRID_PROVIDER:
return 'mutation messagingUpdateSendgridProvider($providerId: String!, $name: String!, $apiKey: String!) { return 'mutation messagingUpdateSendgridProvider($providerId: String!, $name: String!, $apiKey: String!, $enabled: Boolean, $fromName: String, $fromEmail: String) {
messagingUpdateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey) { messagingUpdateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey, enabled: $enabled, fromName: $fromName, fromEmail: $fromEmail) {
_id _id
name name
provider provider
@ -1974,8 +1974,8 @@ trait Base
} }
}'; }';
case self::$UPDATE_FCM_PROVIDER: case self::$UPDATE_FCM_PROVIDER:
return 'mutation updateFcmProvider($providerId: String!, $name: String!, $serverKey: String!) { return 'mutation updateFcmProvider($providerId: String!, $name: String!, $serviceAccountJSON: Json) {
messagingUpdateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) { messagingUpdateFcmProvider(providerId: $providerId, name: $name, serviceAccountJSON: $serviceAccountJSON) {
_id _id
name name
provider provider
@ -2098,8 +2098,8 @@ trait Base
} }
}'; }';
case self::$CREATE_EMAIL: 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) { 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, scheduledAt: $scheduledAt) { 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 _id
topics topics
users users
@ -2178,8 +2178,8 @@ trait Base
} }
}'; }';
case self::$UPDATE_EMAIL: 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) { 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, scheduledAt: $scheduledAt) { 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 _id
topics topics
users users

View file

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

View file

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

View file

@ -5,6 +5,7 @@ namespace Tests\E2E\Services\Messaging;
use Tests\E2E\Client; use Tests\E2E\Client;
use Utopia\App; use Utopia\App;
use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\DSN\DSN; use Utopia\DSN\DSN;
trait MessagingBase trait MessagingBase
@ -23,7 +24,8 @@ trait MessagingBase
'name' => 'Mailgun1', 'name' => 'Mailgun1',
'apiKey' => 'my-apikey', 'apiKey' => 'my-apikey',
'domain' => 'my-domain', 'domain' => 'my-domain',
'from' => 'sender-email@my-domain.com', 'fromName' => 'sender name',
'fromEmail' => 'sender-email@my-domain.com',
'isEuRegion' => false, 'isEuRegion' => false,
], ],
'twilio' => [ 'twilio' => [
@ -64,7 +66,12 @@ trait MessagingBase
'fcm' => [ 'fcm' => [
'providerId' => ID::unique(), 'providerId' => ID::unique(),
'name' => 'FCM1', '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' => [ 'apns' => [
'providerId' => ID::unique(), 'providerId' => ID::unique(),
@ -84,6 +91,7 @@ trait MessagingBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'], 'x-appwrite-key' => $this->getProject()['apiKey'],
]), $providersParams[$key]); ]), $providersParams[$key]);
$this->assertEquals(201, $response['headers']['status-code']); $this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals($providersParams[$key]['name'], $response['body']['name']); $this->assertEquals($providersParams[$key]['name'], $response['body']['name']);
\array_push($providers, $response['body']); \array_push($providers, $response['body']);
@ -134,7 +142,12 @@ trait MessagingBase
], ],
'fcm' => [ 'fcm' => [
'name' => 'FCM2', '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' => [ 'apns' => [
'name' => 'APNS2', 'name' => 'APNS2',
@ -161,11 +174,11 @@ trait MessagingBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'], 'x-appwrite-key' => $this->getProject()['apiKey'],
], [ ], [
'name' => 'Mailgun2', 'name' => 'Mailgun2',
'apiKey' => 'my-apikey', 'apiKey' => 'my-apikey',
'domain' => 'my-domain', 'domain' => 'my-domain',
'isEuRegion' => true, 'isEuRegion' => true,
'enabled' => false, 'enabled' => false,
]); ]);
$this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('Mailgun2', $response['body']['name']); $this->assertEquals('Mailgun2', $response['body']['name']);
@ -229,7 +242,6 @@ trait MessagingBase
], [ ], [
'topicId' => ID::unique(), 'topicId' => ID::unique(),
'name' => 'my-app', 'name' => 'my-app',
'description' => 'web app'
]); ]);
$this->assertEquals(201, $response['headers']['status-code']); $this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals('my-app', $response['body']['name']); $this->assertEquals('my-app', $response['body']['name']);
@ -272,6 +284,32 @@ trait MessagingBase
$this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, \count($response['body']['topics'])); $this->assertEquals(1, \count($response['body']['topics']));
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'queries' => [
'equal("total", [0])'
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, \count($response['body']['topics']));
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'queries' => [
'greaterThan("total", 0)'
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(0, \count($response['body']['topics']));
return $topicId; return $topicId;
} }
@ -552,17 +590,16 @@ trait MessagingBase
$emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'));
$to = $emailDSN->getParam('to'); $to = $emailDSN->getParam('to');
$from = $emailDSN->getParam('from'); $fromName = $emailDSN->getParam('fromName');
$isEuRegion = $emailDSN->getParam('isEuRegion'); $fromEmail = $emailDSN->getParam('fromEmail');
$apiKey = $emailDSN->getPassword(); $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'); $this->markTestSkipped('Email provider not configured');
} }
// Create provider // 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', 'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'], 'x-appwrite-key' => $this->getProject()['apiKey'],
@ -570,9 +607,8 @@ trait MessagingBase
'providerId' => ID::unique(), 'providerId' => ID::unique(),
'name' => 'Mailgun-provider', 'name' => 'Mailgun-provider',
'apiKey' => $apiKey, 'apiKey' => $apiKey,
'domain' => $domain, 'fromName' => $fromName,
'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), 'fromEmail' => $fromEmail
'from' => $from
]); ]);
$this->assertEquals(201, $provider['headers']['status-code']); $this->assertEquals(201, $provider['headers']['status-code']);
@ -604,27 +640,13 @@ trait MessagingBase
$this->assertEquals(201, $user['headers']['status-code']); $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 // Create Subscriber
$subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([
'content-type' => 'application/json', 'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'subscriberId' => ID::unique(), 'subscriberId' => ID::unique(),
'targetId' => $target['body']['$id'], 'targetId' => $user['body']['targets'][0]['$id'],
]); ]);
$this->assertEquals(201, $subscriber['headers']['status-code']); $this->assertEquals(201, $subscriber['headers']['status-code']);
@ -637,13 +659,13 @@ trait MessagingBase
], [ ], [
'messageId' => ID::unique(), 'messageId' => ID::unique(),
'topics' => [$topic['body']['$id']], 'topics' => [$topic['body']['$id']],
'subject' => 'Khali beats Undertaker', 'subject' => 'New blog post',
'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'content' => 'Check out the new blog post at http://localhost',
]); ]);
$this->assertEquals(201, $email['headers']['status-code']); $this->assertEquals(201, $email['headers']['status-code']);
\sleep(5); \sleep(2);
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'], [ $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'], [
'origin' => 'http://localhost', 'origin' => 'http://localhost',
@ -662,7 +684,7 @@ trait MessagingBase
/** /**
* @depends testSendEmail * @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'], [ $message = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/email/' . $email['body']['$id'], [
'content-type' => 'application/json', 'content-type' => 'application/json',
@ -721,8 +743,8 @@ trait MessagingBase
$smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'));
$to = $smsDSN->getParam('to'); $to = $smsDSN->getParam('to');
$from = $smsDSN->getParam('from'); $from = $smsDSN->getParam('from');
$authKey = $smsDSN->getPassword();
$senderId = $smsDSN->getUser(); $senderId = $smsDSN->getUser();
$authKey = $smsDSN->getPassword();
if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) {
$this->markTestSkipped('SMS provider not configured'); $this->markTestSkipped('SMS provider not configured');
@ -735,7 +757,7 @@ trait MessagingBase
'x-appwrite-key' => $this->getProject()['apiKey'], 'x-appwrite-key' => $this->getProject()['apiKey'],
]), [ ]), [
'providerId' => ID::unique(), 'providerId' => ID::unique(),
'name' => 'Msg91-1', 'name' => 'Msg91Sender',
'senderId' => $senderId, 'senderId' => $senderId,
'authKey' => $authKey, 'authKey' => $authKey,
'from' => $from 'from' => $from
@ -847,7 +869,7 @@ trait MessagingBase
'messageId' => ID::unique(), 'messageId' => ID::unique(),
'status' => 'draft', 'status' => 'draft',
'topics' => [$sms['body']['topics'][0]], 'topics' => [$sms['body']['topics'][0]],
'content' => '047487', 'content' => 'Your OTP code is 123456',
]); ]);
$this->assertEquals(201, $sms['headers']['status-code']); $this->assertEquals(201, $sms['headers']['status-code']);
@ -862,7 +884,7 @@ trait MessagingBase
$this->assertEquals(200, $sms['headers']['status-code']); $this->assertEquals(200, $sms['headers']['status-code']);
\sleep(5); \sleep(2);
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $sms['body']['$id'], [ $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $sms['body']['$id'], [
'origin' => 'http://localhost', 'origin' => 'http://localhost',
@ -882,11 +904,11 @@ trait MessagingBase
$this->markTestSkipped('Push DSN empty'); $this->markTestSkipped('Push DSN empty');
} }
$pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); $dsn = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'));
$to = $pushDSN->getParam('to'); $to = $dsn->getParam('to');
$serverKey = $pushDSN->getPassword(); $serviceAccountJSON = $dsn->getParam('serviceAccountJSON');
if (empty($to) || empty($serverKey)) { if (empty($to) || empty($serviceAccountJSON)) {
$this->markTestSkipped('Push provider not configured'); $this->markTestSkipped('Push provider not configured');
} }
@ -898,7 +920,7 @@ trait MessagingBase
]), [ ]), [
'providerId' => ID::unique(), 'providerId' => ID::unique(),
'name' => 'FCM-1', 'name' => 'FCM-1',
'serverKey' => $serverKey, 'serviceAccountJSON' => $serviceAccountJSON,
]); ]);
$this->assertEquals(201, $provider['headers']['status-code']); $this->assertEquals(201, $provider['headers']['status-code']);

View file

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