From 48b91e39a81ef6a54ef7a8d4127090065911cd0d Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Wed, 4 Oct 2023 16:15:59 +0530 Subject: [PATCH] review changes --- app/config/collections.php | 7 + app/controllers/api/account.php | 49 +- app/controllers/api/messaging.php | 1376 ++++++++--------- app/controllers/api/users.php | 1 - app/workers/messaging.php | 156 +- composer.json | 2 +- composer.lock | 95 +- src/Appwrite/Event/Messaging.php | 1 - .../Account/AccountCustomClientTest.php | 8 +- tests/e2e/Services/GraphQL/AccountTest.php | 6 +- .../e2e/Services/Messaging/MessagingBase.php | 4 +- 11 files changed, 851 insertions(+), 854 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 9aec38a3d9..1a56b160c6 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1934,6 +1934,13 @@ $commonCollections = [ 'attributes' => ['providerInternalId'], 'lengths' => [], 'orders' => [], + ], + [ + '$id' => ID::custom('_key_identifier'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['identifier'], + 'lengths' => [], + 'orders' => [], ] ], ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 7115bf5686..228a0621cd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1230,7 +1230,6 @@ App::post('/v1/account/sessions/phone') ->label('abuse-key', 'url:{url},phone:{param-phone}') ->param('userId', '', new CustomId(), 'Unique 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('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') - ->param('from', '', new Text(128), 'Sender of the message. It can be alphanumeric (Ex: MyCompany20). Restrictions may apply depending of the destination.', true) ->inject('request') ->inject('response') ->inject('user') @@ -1239,9 +1238,9 @@ App::post('/v1/account/sessions/phone') ->inject('events') ->inject('messaging') ->inject('locale') - ->action(function (string $userId, string $phone, string $from, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $events, Messaging $messaging, Locale $locale) { + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $events, Messaging $messaging, Locale $locale) { $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('default', [true, false]), + Query::equal('default', [true]), Query::equal('type', ['sms']) ])); if ($provider === false || $provider->isEmpty()) { @@ -1330,13 +1329,20 @@ App::post('/v1/account/sessions/phone') $message = $message->setParam('{{token}}', $secret); $message = $message->render(); - $target = $dbForProject->createDocument('targets', new Document([ - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), - 'identifier' => $phone, - ])); + $target = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$phone]), + Query::equal('providerInternalId', [$provider->getInternalId()]) + ]); + + if (!$target) { + $target = $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerId' => $provider->getId(), + 'providerInternalId' => $provider->getInternalId(), + 'identifier' => $phone, + ])); + } $messageDoc = $dbForProject->createDocument('messages', new Document([ '$id' => $token->getId(), @@ -2899,7 +2905,6 @@ App::post('/v1/account/verification/phone') ->label('sdk.response.model', Response::MODEL_TOKEN) ->label('abuse-limit', 10) ->label('abuse-key', 'userId:{userId}') - ->param('from', '', new Text(128), 'Sender of the message. It can be alphanumeric (Ex: MyCompany20). Restrictions may apply depending of the destination.', true) ->inject('request') ->inject('response') ->inject('user') @@ -2908,9 +2913,9 @@ App::post('/v1/account/verification/phone') ->inject('messaging') ->inject('project') ->inject('locale') - ->action(function (string $from, Request $request, Response $response, Document $user, Database $dbForProject, Event $events, Messaging $messaging, Document $project, Locale $locale) { + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events, Messaging $messaging, Document $project, Locale $locale) { $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('default', [true, false]), + Query::equal('default', [true]), Query::equal('type', ['sms']) ])); if ($provider === false || $provider->isEmpty()) { @@ -2959,13 +2964,17 @@ App::post('/v1/account/verification/phone') $message = $message->setParam('{{token}}', $secret); $message = $message->render(); - $target = $dbForProject->createDocument('targets', new Document([ - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), - 'identifier' => $user->getAttribute('phone'), - ])); + $target = $dbForProject->findOne('targets', [Query::equal('identifier', [$user->getAttribute('phone')]), Query::equal('providerInternalId', [$provider->getInternalId()])]); + + if (!$target) { + $target = $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerId' => $provider->getId(), + 'providerInternalId' => $provider->getInternalId(), + 'identifier' => $user->getAttribute('phone'), + ])); + } $messageDoc = $dbForProject->createDocument('messages', new Document([ '$id' => $verification->getId(), diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 9abf1d492f..f2a61cde36 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -22,6 +22,541 @@ use Utopia\Validator\JSON; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +App::post('/v1/messaging/providers/mailgun') + ->desc('Create Mailgun Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createMailgunProvider') + ->label('sdk.description', '/docs/references/messaging/create-mailgun-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('isEuRegion', false, new Boolean(), 'Set as EU region.', true) + ->param('from', '', new Text(256), 'Sender Email Address.') + ->param('apiKey', '', new Text(0), 'Mailgun API Key.') + ->param('domain', '', new Text(0), 'Mailgun Domain.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, bool $isEuRegion, string $from, string $apiKey, string $domain, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'mailgun', + 'type' => 'email', + 'default' => $default, + 'enabled' => $enabled, + 'search' => $providerId . ' ' . $name . ' ' . 'mailgun' . ' ' . 'email', + 'credentials' => [ + 'apiKey' => $apiKey, + 'domain' => $domain, + 'isEuRegion' => $isEuRegion, + ], + 'options' => [ + 'from' => $from, + ] + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['email']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/sendgrid') + ->desc('Create Sendgrid Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createSendgridProvider') + ->label('sdk.description', '/docs/references/messaging/create-sengrid-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('apiKey', '', new Text(0), 'Sendgrid API key.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $apiKey, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'sendgrid', + 'type' => 'email', + 'default' => $default, + 'enabled' => $enabled, + 'options' => [], + 'search' => $providerId . ' ' . $name . ' ' . 'sendgrid' . ' ' . 'email', + 'credentials' => [ + 'apiKey' => $apiKey, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['sms']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/msg91') + ->desc('Create Msg91 Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createMsg91Provider') + ->label('sdk.description', '/docs/references/messaging/create-msg91-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Text(256), 'Sender Number.') + ->param('senderId', '', new Text(0), 'Msg91 Sender ID.') + ->param('authKey', '', new Text(0), 'Msg91 Auth Key.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $from, string $senderId, string $authKey, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'msg91', + 'type' => 'sms', + 'search' => $providerId . ' ' . $name . ' ' . 'msg91' . ' ' . 'sms', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'senderId' => $senderId, + 'authKey' => $authKey, + ], + 'options' => [ + 'from' => $from, + ] + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['sms']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); +App::post('/v1/messaging/providers/telesign') + ->desc('Create Telesign Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createTelesignProvider') + ->label('sdk.description', '/docs/references/messaging/create-telesign-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('username', '', new Text(0), 'Telesign username.') + ->param('password', '', new Text(0), 'Telesign password.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $username, string $password, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'telesign', + 'type' => 'sms', + 'search' => $providerId . ' ' . $name . ' ' . 'telesign' . ' ' . 'sms', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'username' => $username, + 'password' => $password, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['sms']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/textmagic') + ->desc('Create Textmagic Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createTextmagicProvider') + ->label('sdk.description', '/docs/references/messaging/create-textmagic-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('username', '', new Text(0), 'Textmagic username.') + ->param('apiKey', '', new Text(0), 'Textmagic apiKey.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $username, string $apiKey, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'text-magic', + 'type' => 'sms', + 'search' => $providerId . ' ' . $name . ' ' . 'text-magic' . ' ' . 'sms', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'username' => $username, + 'apiKey' => $apiKey, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['sms']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/twilio') + ->desc('Create Twilio Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createTwilioProvider') + ->label('sdk.description', '/docs/references/messaging/create-twilio-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('accountSid', '', new Text(0), 'Twilio account secret ID.') + ->param('authToken', '', new Text(0), 'Twilio authentication token.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $accountSid, string $authToken, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'twilio', + 'type' => 'sms', + 'search' => $providerId . ' ' . $name . ' ' . 'twilio' . ' ' . 'sms', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'accountSid' => $accountSid, + 'authToken' => $authToken, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['sms']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/vonage') + ->desc('Create Vonage Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createVonageProvider') + ->label('sdk.description', '/docs/references/messaging/create-vonage-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('apiKey', '', new Text(0), 'Vonage API key.') + ->param('apiSecret', '', new Text(0), 'Vonage API secret.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $apiKey, string $apiSecret, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'vonage', + 'type' => 'sms', + 'search' => $providerId . ' ' . $name . ' ' . 'vonage' . ' ' . 'sms', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'apiKey' => $apiKey, + 'apiSecret' => $apiSecret, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['sms']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/fcm') + ->desc('Create FCM Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createFCMProvider') + ->label('sdk.description', '/docs/references/messaging/create-fcm-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('serverKey', '', new Text(0), 'FCM Server Key.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $serverKey, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'fcm', + 'type' => 'push', + 'search' => $providerId . ' ' . $name . ' ' . 'fcm' . ' ' . 'push', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'serverKey' => $serverKey, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['pushq']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/apns') + ->desc('Create APNS Provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'providers.create') + ->label('audits.resource', 'providers/{response.$id}') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createAPNSProvider') + ->label('sdk.description', '/docs/references/messaging/create-apns-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('default', false, new Boolean(), 'Set as default provider.', true) + ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('authKey', '', new Text(0), 'APNS authentication key.') + ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.') + ->param('teamId', '', new Text(0), 'APNS team ID.') + ->param('bundleId', '', new Text(0), 'APNS bundle ID.') + ->param('endpoint', '', new Text(0), 'APNS endpoint.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'apns', + 'type' => 'push', + 'search' => $providerId . ' ' . $name . ' ' . 'apns' . ' ' . 'push', + 'default' => $default, + 'enabled' => $enabled, + 'credentials' => [ + 'authKey' => $authKey, + 'authKeyId' => $authKeyId, + 'teamId' => $teamId, + 'bundleId' => $bundleId, + 'endpoint' => $endpoint, + ], + ]); + + // Check if a default provider exists, if not, set this one as default + if ( + empty($dbForProject->findOne('providers', [ + Query::equal('default', [true]), + Query::equal('type', ['push']) + ])) + ) { + $provider->setAttribute('default', true); + } + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + App::get('/v1/messaging/providers') ->desc('List Providers') ->groups(['api', 'messaging']) @@ -45,9 +580,8 @@ App::get('/v1/messaging/providers') if ($cursor) { $providerId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->find('providers', [ + $cursorDocument = Authorization::skip(fn () => $dbForProject->findOne('providers', [ Query::equal('$id', [$providerId]), - Query::limit(1), ])); if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) { @@ -88,74 +622,7 @@ App::get('/v1/messaging/providers/:id') $response->dynamic($provider, Response::MODEL_PROVIDER); }); -/** - * Email Providers - */ -App::post('/v1/messaging/providers/mailgun') - ->desc('Create Mailgun Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderMailgun') - ->label('sdk.description', '/docs/references/messaging/create-provider-mailgun.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('isEuRegion', false, new Boolean(), 'Set as EU region.', true) - ->param('from', '', new Text(256), 'Sender Email Address.') - ->param('apiKey', '', new Text(0), 'Mailgun API Key.') - ->param('domain', '', new Text(0), 'Mailgun Domain.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, bool $isEuRegion, string $from, string $apiKey, string $domain, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'mailgun', - 'type' => 'email', - 'default' => $default, - 'enabled' => $enabled, - 'search' => $providerId . ' ' . $name . ' ' . 'mailgun' . ' ' . 'email', - 'credentials' => [ - 'apiKey' => $apiKey, - 'domain' => $domain, - 'isEuRegion' => $isEuRegion, - ], - 'options' => [ - 'from' => $from, - ] - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/mailgun') +App::patch('/v1/messaging/providers/mailgun/:id') ->desc('Update Mailgun Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -163,8 +630,8 @@ App::patch('/v1/messaging/providers/:id/mailgun') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderMailgun') - ->label('sdk.description', '/docs/references/messaging/update-provider-mailgun.md') + ->label('sdk.method', 'updateMailgunProvider') + ->label('sdk.description', '/docs/references/messaging/update-mailgun-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -194,15 +661,6 @@ App::patch('/v1/messaging/providers/:id/mailgun') $provider->setAttribute('search', $provider->getId() . ' ' . $name . ' ' . 'mailgun' . ' ' . 'email'); } - if ($isEuRegion === true || $isEuRegion === false) { - $credentials = $provider->getAttribute('credentials'); - $provider->setAttribute('credentials', [ - 'isEuRegion' => $isEuRegion, - 'apiKey' => $credentials['apiKey'], - 'domain' => $credentials['domain'], - ]); - } - if (!empty($from)) { $provider->setAttribute('options', [ 'from' => $from, @@ -213,83 +671,29 @@ App::patch('/v1/messaging/providers/:id/mailgun') $provider->setAttribute('enabled', $enabled); } - if ($apiKey || $domain) { - // Check if all credential variables are present - if ($apiKey && $domain) { - $provider->setAttribute('credentials', [ - 'apiKey' => $apiKey, - 'domain' => $domain, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if ($isEuRegion === true || $isEuRegion === false) { + $credentials['isEuRegion'] = $isEuRegion; } + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; + } + + if (!empty($domain)) { + $credentials['domain'] = $domain; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/sendgrid') - ->desc('Create Sendgrid Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderSendgrid') - ->label('sdk.description', '/docs/references/messaging/create-provider-sendgrid.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('apiKey', '', new Text(0), 'Sendgrid API key.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $apiKey, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'sendgrid', - 'type' => 'email', - 'default' => $default, - 'enabled' => $enabled, - 'options' => [], - 'search' => $providerId . ' ' . $name . ' ' . 'sendgrid' . ' ' . 'email', - 'credentials' => [ - 'apiKey' => $apiKey, - ], - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/sendgrid') +App::patch('/v1/messaging/providers/sendgrid/:id') ->desc('Update Sendgrid Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -297,8 +701,8 @@ App::patch('/v1/messaging/providers/:id/sendgrid') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderSendgrid') - ->label('sdk.description', '/docs/references/messaging/update-provider-sendgrid.md') + ->label('sdk.method', 'updateSendgridProvider') + ->label('sdk.description', '/docs/references/messaging/update-sendgrid-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -329,84 +733,19 @@ App::patch('/v1/messaging/providers/:id/sendgrid') $provider->setAttribute('enabled', $enabled); } - if ($apiKey) { + if (!empty($apiKey)) { $provider->setAttribute('credentials', [ 'apiKey' => $apiKey, ]); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -/** - * SMS Providers - */ -App::post('/v1/messaging/providers/msg91') - ->desc('Create Msg91 Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderMsg91') - ->label('sdk.description', '/docs/references/messaging/create-provider-msg91.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('from', '', new Text(256), 'Sender Number.') - ->param('senderId', '', new Text(0), 'Msg91 Sender ID.') - ->param('authKey', '', new Text(0), 'Msg91 Auth Key.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $from, string $senderId, string $authKey, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'msg91', - 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'msg91' . ' ' . 'sms', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'senderId' => $senderId, - 'authKey' => $authKey, - ], - 'options' => [ - 'from' => $from, - ] - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/msg91') +App::patch('/v1/messaging/providers/msg91/:id') ->desc('Update Msg91 Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -414,8 +753,8 @@ App::patch('/v1/messaging/providers/:id/msg91') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderMsg91') - ->label('sdk.description', '/docs/references/messaging/update-provider-msg91.md') + ->label('sdk.method', 'updateMsg91Provider') + ->label('sdk.description', '/docs/references/messaging/update-msg91-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -447,84 +786,25 @@ App::patch('/v1/messaging/providers/:id/msg91') $provider->setAttribute('enabled', $enabled); } - if ($senderId || $authKey) { - // Check if all credential variables are present - if ($senderId && $authKey) { - $provider->setAttribute('credentials', [ - 'senderId' => $senderId, - 'authKey' => $authKey, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if (!empty($senderId)) { + $credentials['senderId'] = $senderId; } + if (!empty($authKey)) { + $credentials['authKey'] = $authKey; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/telesign') - ->desc('Create Telesign Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderTelesign') - ->label('sdk.description', '/docs/references/messaging/create-provider-telesign.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('username', '', new Text(0), 'Telesign username.') - ->param('password', '', new Text(0), 'Telesign password.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $username, string $password, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'telesign', - 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'telesign' . ' ' . 'sms', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'username' => $username, - 'password' => $password, - ], - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/telesign') +App::patch('/v1/messaging/providers/telesign/:id') ->desc('Update Telesign Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -532,8 +812,8 @@ App::patch('/v1/messaging/providers/:id/telesign') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderTelesign') - ->label('sdk.description', '/docs/references/messaging/update-provider-telesign.md') + ->label('sdk.method', 'updateTelesignProvider') + ->label('sdk.description', '/docs/references/messaging/update-telesign-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -565,84 +845,25 @@ App::patch('/v1/messaging/providers/:id/telesign') $provider->setAttribute('enabled', $enabled); } - if ($username || $password) { - // Check if all credential variables are present - if ($username && $password) { - $provider->setAttribute('credentials', [ - 'username' => $username, - 'password' => $password, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if (!empty($username)) { + $credentials['username'] = $username; } + if (!empty($password)) { + $credentials['password'] = $password; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/textmagic') - ->desc('Create Textmagic Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderTextmagic') - ->label('sdk.description', '/docs/references/messaging/create-provider-textmagic.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('username', '', new Text(0), 'Textmagic username.') - ->param('apiKey', '', new Text(0), 'Textmagic apiKey.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $username, string $apiKey, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'text-magic', - 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'text-magic' . ' ' . 'sms', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'username' => $username, - 'apiKey' => $apiKey, - ], - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/textmagic') +App::patch('/v1/messaging/providers/textmagic/:id') ->desc('Update Textmagic Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -650,8 +871,8 @@ App::patch('/v1/messaging/providers/:id/textmagic') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderTextmagic') - ->label('sdk.description', '/docs/references/messaging/update-provider-textmagic.md') + ->label('sdk.method', 'updateTextmagicProvider') + ->label('sdk.description', '/docs/references/messaging/update-textmagic-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -683,84 +904,25 @@ App::patch('/v1/messaging/providers/:id/textmagic') $provider->setAttribute('enabled', $enabled); } - if ($username || $apiKey) { - // Check if all credential variables are present - if ($username && $apiKey) { - $provider->setAttribute('credentials', [ - 'username' => $username, - 'apiKey' => $apiKey, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if (!empty($username)) { + $credentials['username'] = $username; } + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/twilio') - ->desc('Create Twilio Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderTwilio') - ->label('sdk.description', '/docs/references/messaging/create-provider-twilio.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('accountSid', '', new Text(0), 'Twilio account secret ID.') - ->param('authToken', '', new Text(0), 'Twilio authentication token.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $accountSid, string $authToken, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'twilio', - 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'twilio' . ' ' . 'sms', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'accountSid' => $accountSid, - 'authToken' => $authToken, - ], - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/twilio') +App::patch('/v1/messaging/providers/twilio/:id') ->desc('Update Twilio Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -768,8 +930,8 @@ App::patch('/v1/messaging/providers/:id/twilio') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderTwilio') - ->label('sdk.description', '/docs/references/messaging/update-provider-twilio.md') + ->label('sdk.method', 'updateTwilioProvider') + ->label('sdk.description', '/docs/references/messaging/update-twilio-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -801,84 +963,25 @@ App::patch('/v1/messaging/providers/:id/twilio') $provider->setAttribute('enabled', $enabled); } - if ($accountSid || $authToken) { - // Check if all credential variables are present - if ($accountSid && $authToken) { - $provider->setAttribute('credentials', [ - 'accountSid' => $accountSid, - 'authToken' => $authToken, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if (!empty($accountSid)) { + $credentials['accountSid'] = $accountSid; } + if (!empty($authToken)) { + $credentials['authToken'] = $authToken; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/vonage') - ->desc('Create Vonage Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderVonage') - ->label('sdk.description', '/docs/references/messaging/create-provider-vonage.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('apiKey', '', new Text(0), 'Vonage API key.') - ->param('apiSecret', '', new Text(0), 'Vonage API secret.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $apiKey, string $apiSecret, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'vonage', - 'type' => 'sms', - 'search' => $providerId . ' ' . $name . ' ' . 'vonage' . ' ' . 'sms', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'apiKey' => $apiKey, - 'apiSecret' => $apiSecret, - ], - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/vonage') +App::patch('/v1/messaging/providers/vonage/:id') ->desc('Update Vonage Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -886,8 +989,8 @@ App::patch('/v1/messaging/providers/:id/vonage') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderVonage') - ->label('sdk.description', '/docs/references/messaging/update-provider-vonage.md') + ->label('sdk.method', 'updateVonageProvider') + ->label('sdk.description', '/docs/references/messaging/update-vonage-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -919,85 +1022,25 @@ App::patch('/v1/messaging/providers/:id/vonage') $provider->setAttribute('enabled', $enabled); } - if ($apiKey || $apiSecret) { - // Check if all credential variables are present - if ($apiKey && $apiSecret) { - $provider->setAttribute('credentials', [ - 'apiKey' => $apiKey, - 'apiSecret' => $apiSecret, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; } + if (!empty($apiSecret)) { + $credentials['apiSecret'] = $apiSecret; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -/** - * Push Providers - */ -App::post('/v1/messaging/providers/fcm') - ->desc('Create FCM Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderFCM') - ->label('sdk.description', '/docs/references/messaging/create-provider-fcm.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('serverKey', '', new Text(0), 'FCM Server Key.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $serverKey, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'fcm', - 'type' => 'push', - 'search' => $providerId . ' ' . $name . ' ' . 'fcm' . ' ' . 'push', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'serverKey' => $serverKey, - ], - ]); - - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/fcm') +App::patch('/v1/messaging/providers/fcm/:id') ->desc('Update FCM Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -1005,8 +1048,8 @@ App::patch('/v1/messaging/providers/:id/fcm') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderFCM') - ->label('sdk.description', '/docs/references/messaging/update-provider-fcm.md') + ->label('sdk.method', 'updateFCMProvider') + ->label('sdk.description', '/docs/references/messaging/update-fcm-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -1037,81 +1080,18 @@ App::patch('/v1/messaging/providers/:id/fcm') $provider->setAttribute('enabled', $enabled); } - if ($serverKey) { + if (!empty($serverKey)) { $provider->setAttribute('credentials', ['serverKey' => $serverKey]); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/apns') - ->desc('Create APNS Provider') - ->groups(['api', 'messaging']) - ->label('audits.event', 'providers.create') - ->label('audits.resource', 'providers/{response.$id}') - ->label('scope', 'providers.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createProviderAPNS') - ->label('sdk.description', '/docs/references/messaging/create-provider-apns.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROVIDER) - ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Provider name.') - ->param('default', false, new Boolean(), 'Set as default provider.', true) - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) - ->param('authKey', '', new Text(0), 'APNS authentication key.') - ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.') - ->param('teamId', '', new Text(0), 'APNS team ID.') - ->param('bundleId', '', new Text(0), 'APNS bundle ID.') - ->param('endpoint', '', new Text(0), 'APNS endpoint.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $providerId, string $name, bool $default, bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Database $dbForProject, Response $response) { - $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $provider = new Document([ - '$id' => $providerId, - 'name' => $name, - 'provider' => 'apns', - 'type' => 'push', - 'search' => $providerId . ' ' . $name . ' ' . 'apns' . ' ' . 'push', - 'default' => $default, - 'enabled' => $enabled, - 'credentials' => [ - 'authKey' => $authKey, - 'authKeyId' => $authKeyId, - 'teamId' => $teamId, - 'bundleId' => $bundleId, - 'endpoint' => $endpoint, - ], - ]); - // Check if a default provider exists, if not, set this one as default - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), - ])) - ) { - $provider->setAttribute('default', true); - } - - try { - $provider = $dbForProject->createDocument('providers', $provider); - } catch (DuplicateException) { - throw new Exception(Exception::PROVIDER_ALREADY_EXISTS, 'Provider already exists.'); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($provider, Response::MODEL_PROVIDER); - }); - -App::patch('/v1/messaging/providers/:id/apns') +App::patch('/v1/messaging/providers/apns/:id') ->desc('Update APNS Provider') ->groups(['api', 'messaging']) ->label('audits.event', 'providers.update') @@ -1119,8 +1099,8 @@ App::patch('/v1/messaging/providers/:id/apns') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateProviderAPNS') - ->label('sdk.description', '/docs/references/messaging/update-provider-apns.md') + ->label('sdk.method', 'updateAPNSProvider') + ->label('sdk.description', '/docs/references/messaging/update-apns-provider.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) @@ -1155,24 +1135,31 @@ App::patch('/v1/messaging/providers/:id/apns') $provider->setAttribute('enabled', $enabled); } - if ($authKey || $authKeyId || $teamId || $bundleId || $endpoint) { - // Check if all credential variables are present - if ($authKey && $authKeyId && $teamId && $bundleId && $endpoint) { - $provider->setAttribute('credentials', [ - 'authKey' => $authKey, - 'authKeyId' => $authKeyId, - 'teamId' => $teamId, - 'bundleId' => $bundleId, - 'endpoint' => $endpoint, - ]); - } else { - // Not all credential params are present - throw new Exception(Exception::DOCUMENT_MISSING_DATA); - } + $credentials = $provider->getAttribute('credentials'); + + if (!empty($authKey)) { + $credentials['authKey'] = $authKey; } + if (!empty($authKeyId)) { + $credentials['authKeyId'] = $authKeyId; + } + + if (!empty($teamId)) { + $credentials['teamId'] = $teamId; + } + + if (!empty($bundleId)) { + $credentials['bundle'] = $bundleId; + } + + if (!empty($endpoint)) { + $credentials['endpoint'] = $endpoint; + } + + $provider->setAttribute('credentials', $credentials); + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $response ->dynamic($provider, Response::MODEL_PROVIDER); @@ -1201,36 +1188,11 @@ App::delete('/v1/messaging/providers/:id') throw new Exception(Exception::PROVIDER_NOT_FOUND); } - $dbForProject->deleteCachedDocument('providers', $provider->getId()); $dbForProject->deleteDocument('providers', $provider->getId()); $response->noContent(); }); -App::get('/v1/messaging/messages/:id') - ->desc('Get Message') - ->groups(['api', 'messaging']) - ->label('scope', 'messages.read') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'getMessage') - ->label('sdk.description', '/docs/references/messaging/get-message.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MESSAGE) - ->param('id', '', new UID(), 'Message ID.') - ->inject('dbForProject') - ->inject('response') - ->action(function (string $id, Database $dbForProject, Response $response) { - $message = $dbForProject->getDocument('messages', $id); - - if ($message->isEmpty()) { - throw new Exception(Exception::MESSAGE_NOT_FOUND); - } - - $response->dynamic($message, Response::MODEL_MESSAGE); - }); - App::post('/v1/messaging/messages/email') ->desc('Send an email.') ->groups(['api', 'messaging']) @@ -1245,12 +1207,12 @@ App::post('/v1/messaging/messages/email') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->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('providerId', '', new Text(128), 'Email Provider ID.') + ->param('providerId', '', new UID(), 'Email Provider ID.') ->param('to', [], new ArrayList(new Text(65535)), 'List of Topic IDs or List of User IDs or List of Target IDs.') - ->param('subject', '', new Text(128), 'Email Subject.') + ->param('subject', '', new Text(998), 'Email Subject.') ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(65407), 'Email Content.') - ->param('html', false, new Boolean(false), 'Is content of type HTML', true) + ->param('html', false, new Boolean(), 'Is content of type HTML', true) ->inject('dbForProject') ->inject('project') ->inject('messaging') @@ -1280,11 +1242,35 @@ App::post('/v1/messaging/messages/email') ])); $messaging - ->setMessageId($message->getId()) - ->setProject($project) - ->trigger(); + ->setMessageId($message->getId()) + ->setProject($project) + ->trigger(); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($message, Response::MODEL_MESSAGE); }); + +App::get('/v1/messaging/messages/:id') + ->desc('Get Message') + ->groups(['api', 'messaging']) + ->label('scope', 'messages.read') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'getMessage') + ->label('sdk.description', '/docs/references/messaging/get-message.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_MESSAGE) + ->param('id', '', new UID(), 'Message ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, Database $dbForProject, Response $response) { + $message = $dbForProject->getDocument('messages', $id); + + if ($message->isEmpty()) { + throw new Exception(Exception::MESSAGE_NOT_FOUND); + } + + $response->dynamic($message, Response::MODEL_MESSAGE); + }); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9b2dc516cb..244bc1150b 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1228,7 +1228,6 @@ App::patch('/v1/users/:userId/targets/:targetId/identifier') throw new Exception(Exception::USER_TARGET_NOT_FOUND); } - // Update the target identifier here $target->setAttribute('identifier', $identifier); $target = $dbForProject->updateDocument('targets', $target->getId(), $target); diff --git a/app/workers/messaging.php b/app/workers/messaging.php index b5dc79917d..8e25a8210c 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -22,6 +22,9 @@ use Utopia\Messaging\Adapters\Email\SendGrid; use Utopia\Messaging\Messages\Email; use Utopia\Messaging\Messages\Push; use Utopia\Messaging\Messages\SMS; +use Appwrite\Extend\Exception; + +use function Swoole\Coroutine\batch; require_once __DIR__ . '/../init.php'; @@ -39,48 +42,9 @@ class MessagingV1 extends Worker public function getName(): string { - return "mails"; + return "messaging"; } - public function sms($record): ?SMSAdapter - { - $credentials = $record->getAttribute('credentials'); - return match ($record->getAttribute('provider')) { - 'mock' => new Mock('username', 'password'), - 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']), - 'text-magic' => new TextMagic($credentials['username'], $credentials['apiKey']), - 'telesign' => new Telesign($credentials['username'], $credentials['password']), - 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']), - 'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']), - default => null - }; - } - - public function push($record): ?PushAdapter - { - $credentials = $record->getAttribute('credentials'); - return match ($record->getAttribute('provider')) { - 'apns' => new APNS( - $credentials['authKey'], - $credentials['authKeyId'], - $credentials['teamId'], - $credentials['bundleId'], - $credentials['endpoint'] - ), - 'fcm' => new FCM($credentials['serverKey']), - default => null - }; - } - - public function email($record): ?EmailAdapter - { - $credentials = $record->getAttribute('credentials'); - return match ($record->getAttribute('provider')) { - 'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']), - 'sendgrid' => new SendGrid($credentials['apiKey']), - default => null - }; - } public function init(): void { @@ -91,12 +55,11 @@ class MessagingV1 extends Worker $project = new Document($this->args['project']); $this->dbForProject = $this->getProjectDB($project); - $messageRecord = $this->dbForProject->getDocument('messages', $this->args['messageId']); + $message = $this->dbForProject->getDocument('messages', $this->args['messageId']); - $providerId = $messageRecord->getAttribute('providerId'); - $providerRecord = $this->dbForProject->getDocument('providers', $providerId); + $provider = $this->dbForProject->getDocument('providers', $message->getAttribute('providerId')); - $this->processMessage($messageRecord, $providerRecord); + $this->processMessage($message, $provider); } private function processMessage(Document $messageRecord, Document $providerRecord): void @@ -105,7 +68,7 @@ class MessagingV1 extends Worker 'sms' => $this->sms($providerRecord), 'push' => $this->push($providerRecord), 'email' => $this->email($providerRecord), - default => null + default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE) }; $recipientsId = $messageRecord->getAttribute('to'); @@ -115,7 +78,6 @@ class MessagingV1 extends Worker */ $recipients = []; - $topics = $this->dbForProject->find('topics', [Query::equal('$id', $recipientsId)]); foreach ($topics as $topic) { $recipients = \array_merge($recipients, $topic->getAttribute('targets')); @@ -128,6 +90,7 @@ class MessagingV1 extends Worker $targets = $this->dbForProject->find('targets', [Query::equal('$id', $recipientsId)]); $recipients = \array_merge($recipients, $targets); + $recipients = \array_filter($recipients, fn (Document $recipient) => $recipient->getAttribute('providerId') === $providerRecord->getId()); $identifiers = \array_map(function (Document $recipient) { return $recipient->getAttribute('identifier'); @@ -135,27 +98,42 @@ class MessagingV1 extends Worker $maxBatchSize = $provider->getMaxMessagesPerRequest(); $batches = \array_chunk($identifiers, $maxBatchSize); - $deliveredTo = 0; - foreach ($batches as $batch) { - $messageRecord->setAttribute('to', $batch); - $message = match ($providerRecord->getAttribute('type')) { - 'sms' => $this->buildSMSMessage($messageRecord, $providerRecord), - 'push' => $this->buildPushMessage($messageRecord), - 'email' => $this->buildEmailMessage($messageRecord, $providerRecord), - default => null - }; - try { - $provider->send($message); - $deliveredTo += \count($batch); - } catch (Exception $e) { - $deliveryErrors = $messageRecord->getAttribute('deliveryErrors'); - foreach ($batch as $identifier) { - $deliveryErrors[] = 'Failed to send message to target' . $identifier . ': ' . $e->getMessage(); + $results = batch(\array_map(function ($batch) use ($messageRecord, $providerRecord, $provider) { + return function () use ($batch, $messageRecord, $providerRecord, $provider) { + $deliveredTo = 0; + $deliveryErrors = []; + $messageData = clone $messageRecord; + $messageData->setAttribute('to', $batch); + $message = match ($providerRecord->getAttribute('type')) { + 'sms' => $this->buildSMSMessage($messageRecord, $providerRecord), + 'push' => $this->buildPushMessage($messageRecord), + 'email' => $this->buildEmailMessage($messageRecord, $providerRecord), + default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE) + }; + try { + $provider->send($message); + $deliveredTo += \count($batch); + } catch (Exception $e) { + foreach ($batch as $identifier) { + $deliveryErrors[] = 'Failed to send message to target' . $identifier . ': ' . $e->getMessage(); + } + } finally { + return [ + 'deliveredTo' => $deliveredTo, + 'deliveryErrors' => $deliveryErrors, + ]; } - $messageRecord->setAttribute('deliveryErrors', $deliveryErrors); - } + }; + }, $batches)); + + $deliveredTo = 0; + $deliveryErrors = []; + foreach ($results as $result) { + $deliveredTo += $result['deliveredTo']; + $deliveryErrors = \array_merge($deliveryErrors, $result['deliveryErrors']); } + $messageRecord->setAttribute('deliveryErrors', $deliveryErrors); if (\count($messageRecord->getAttribute('deliveryErrors')) > 0) { $messageRecord->setAttribute('status', 'failed'); @@ -173,17 +151,58 @@ class MessagingV1 extends Worker { } - private function buildEmailMessage($message, $provider): Email + private function sms(Document $document): ?SMSAdapter + { + $credentials = $document->getAttribute('credentials'); + return match ($document->getAttribute('provider')) { + 'mock' => new Mock('username', 'password'), + 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']), + 'text-magic' => new TextMagic($credentials['username'], $credentials['apiKey']), + 'telesign' => new Telesign($credentials['username'], $credentials['password']), + 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']), + 'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']), + default => null + }; + } + + private function push(Document $document): ?PushAdapter + { + $credentials = $document->getAttribute('credentials'); + return match ($document->getAttribute('provider')) { + 'apns' => new APNS( + $credentials['authKey'], + $credentials['authKeyId'], + $credentials['teamId'], + $credentials['bundleId'], + $credentials['endpoint'] + ), + 'fcm' => new FCM($credentials['serverKey']), + default => null + }; + } + + private function email(Document $document): ?EmailAdapter + { + $credentials = $document->getAttribute('credentials'); + return match ($document->getAttribute('provider')) { + 'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']), + 'sendgrid' => new SendGrid($credentials['apiKey']), + default => null + }; + } + + private function buildEmailMessage(Document $message, Document $provider): Email { $from = $provider['options']['from']; $to = $message['to']; $subject = $message['data']['subject']; $content = $message['data']['content']; $html = $message['data']['html']; - return new Email(to: $to, subject: $subject, content: $content, from: $from, html: $html); + + return new Email($to, $subject, $content, $from, null, $html); } - private function buildSMSMessage($message, $provider): SMS + private function buildSMSMessage(Document $message, Document $provider): SMS { $to = $message['to']; $content = $message['data']['content']; @@ -192,7 +211,7 @@ class MessagingV1 extends Worker return new SMS($to, $content, $from); } - private function buildPushMessage($message): Push + private function buildPushMessage(Document $message): Push { $to = $message['to']; $title = $message['data']['title']; @@ -204,6 +223,7 @@ class MessagingV1 extends Worker $color = $message['data']['color']; $tag = $message['data']['tag']; $badge = $message['data']['badge']; + return new Push($to, $title, $body, $data, $action, $sound, $icon, $color, $tag, $badge); } } diff --git a/composer.json b/composer.json index 3425c98419..efa0179f3b 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "utopia-php/image": "0.5.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.3.*", - "utopia-php/messaging": "dev-feat-push as 0.1.1", + "utopia-php/messaging": "0.2.*", "utopia-php/migration": "0.3.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.4.*", diff --git a/composer.lock b/composer.lock index e739e122c9..13040c831d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6431be75373bf2e1bdbe2c638188d15f", + "content-hash": "237609b7e9fb20d807aa6e773bf72de6", "packages": [ { "name": "adhocore/jwt", @@ -1050,16 +1050,16 @@ }, { "name": "matomo/device-detector", - "version": "6.1.5", + "version": "6.1.6", "source": { "type": "git", "url": "https://github.com/matomo-org/device-detector.git", - "reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4" + "reference": "5cbea85106e561c7138d03603eb6e05128480409" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/40ca2990dba2c1719e5c62168e822e0b86c167d4", - "reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4", + "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/5cbea85106e561c7138d03603eb6e05128480409", + "reference": "5cbea85106e561c7138d03603eb6e05128480409", "shasum": "" }, "require": { @@ -1115,7 +1115,7 @@ "source": "https://github.com/matomo-org/matomo", "wiki": "https://dev.matomo.org/" }, - "time": "2023-08-17T16:17:41+00:00" + "time": "2023-10-02T10:01:54+00:00" }, { "name": "mongodb/mongodb", @@ -2152,16 +2152,16 @@ }, { "name": "utopia-php/database", - "version": "0.43.2", + "version": "0.43.4", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f2626acd42665a9987c94af1c93bf20c28d55c9d" + "reference": "cabdd02e8dc1732eb0b22007c511e7bb3caa5c8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f2626acd42665a9987c94af1c93bf20c28d55c9d", - "reference": "f2626acd42665a9987c94af1c93bf20c28d55c9d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/cabdd02e8dc1732eb0b22007c511e7bb3caa5c8c", + "reference": "cabdd02e8dc1732eb0b22007c511e7bb3caa5c8c", "shasum": "" }, "require": { @@ -2202,9 +2202,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.43.2" + "source": "https://github.com/utopia-php/database/tree/0.43.4" }, - "time": "2023-09-07T19:04:33+00:00" + "time": "2023-09-28T09:00:05+00:00" }, { "name": "utopia-php/domains", @@ -2516,16 +2516,16 @@ }, { "name": "utopia-php/messaging", - "version": "dev-feat-push", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "2bb09220d0993a9f8f0afc63ff51382b13d93e18" + "reference": "2d0f474a106bb1da285f85e105c29b46085d3a43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/2bb09220d0993a9f8f0afc63ff51382b13d93e18", - "reference": "2bb09220d0993a9f8f0afc63ff51382b13d93e18", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/2d0f474a106bb1da285f85e105c29b46085d3a43", + "reference": "2d0f474a106bb1da285f85e105c29b46085d3a43", "shasum": "" }, "require": { @@ -2558,22 +2558,22 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/feat-push" + "source": "https://github.com/utopia-php/messaging/tree/0.2.0" }, - "time": "2023-09-14T20:29:49+00:00" + "time": "2023-09-14T20:48:42+00:00" }, { "name": "utopia-php/migration", - "version": "0.3.4", + "version": "0.3.5", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "ade836d61b3e1547bc9f0dc300ee75b24ab49f7a" + "reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/ade836d61b3e1547bc9f0dc300ee75b24ab49f7a", - "reference": "ade836d61b3e1547bc9f0dc300ee75b24ab49f7a", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83", + "reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83", "shasum": "" }, "require": { @@ -2596,16 +2596,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Bradley Schofield", - "email": "bradley@appwrite.io" - } - ], "description": "A simple library to migrate resources between services.", "keywords": [ "framework", @@ -2616,9 +2606,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.3.4" + "source": "https://github.com/utopia-php/migration/tree/0.3.5" }, - "time": "2023-09-14T17:17:55+00:00" + "time": "2023-09-25T16:51:47+00:00" }, { "name": "utopia-php/mongo", @@ -3442,16 +3432,16 @@ }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", "shasum": "" }, "require": { @@ -3483,9 +3473,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/instantiator", @@ -4145,16 +4135,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.1", + "version": "1.24.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01" + "reference": "bcad8d995980440892759db0c32acae7c8e79442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", - "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", + "reference": "bcad8d995980440892759db0c32acae7c8e79442", "shasum": "" }, "require": { @@ -4186,9 +4176,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" }, - "time": "2023-09-18T12:18:02+00:00" + "time": "2023-09-26T12:28:12+00:00" }, { "name": "phpunit/php-code-coverage", @@ -6001,18 +5991,9 @@ "time": "2023-08-28T11:09:02+00:00" } ], - "aliases": [ - { - "package": "utopia-php/messaging", - "version": "dev-feat-push", - "alias": "0.1.1", - "alias_normalized": "0.1.1.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/messaging": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index 1df75dfedf..cbfb7981ac 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -15,7 +15,6 @@ class Messaging extends Event } - /** * Sets message ID for the messaging event. * diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 96f2b12308..7f4cb84b05 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -748,7 +748,7 @@ class AccountCustomClientTest extends Scope $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); - if ($to === '' || $from === '' || $authKey === '' || $senderId === '') { + if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { $this->markTestSkipped('SMS provider not configured'); } @@ -758,10 +758,8 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ - 'providerId' => 'unique()', + 'providerId' => ID::unique(), 'name' => 'Sms provider', - 'provider' => 'msg91', - 'type' => 'sms', 'senderId' => $senderId, 'authKey' => $authKey, 'default' => true, @@ -1032,7 +1030,7 @@ class AccountCustomClientTest extends Scope $this->assertEmpty($response['body']['secret']); $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); - \sleep(2); + \sleep(3); $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $response['body']['$id'], [ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/GraphQL/AccountTest.php b/tests/e2e/Services/GraphQL/AccountTest.php index 5970883dbe..dc399cf2b3 100644 --- a/tests/e2e/Services/GraphQL/AccountTest.php +++ b/tests/e2e/Services/GraphQL/AccountTest.php @@ -128,7 +128,7 @@ class AccountTest extends Scope $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); - if ($to === '' || $from === '' || $authKey === '' || $senderId === '') { + if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { $this->markTestSkipped('SMS provider not configured'); } @@ -137,10 +137,8 @@ class AccountTest extends Scope $graphQLPayload = [ 'query' => $query, 'variables' => [ - 'providerId' => 'unique()', + 'providerId' => ID::unique(), 'name' => 'Sms Provider', - 'provider' => 'msg91', - 'type' => 'sms', 'from' => $from, 'senderId' => $senderId, 'authKey' => $authKey, diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index bbc8b56693..05092fdd2e 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -137,7 +137,7 @@ trait MessagingBase ], ]; foreach (\array_keys($providersParams) as $index => $key) { - $response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/' . $providers[$index]['$id'] . '/' . $key, [ + $response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/' . $key . '/' . $providers[$index]['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -147,7 +147,7 @@ trait MessagingBase $this->assertEquals($providersParams[$key]['name'], $response['body']['name']); } - $response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/' . $providers[1]['$id'] . '/mailgun', [ + $response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/mailgun/' . $providers[1]['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'],