From 520d19e3352fc75cb651fc5d846af3cc4711aecc Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 21 Nov 2023 12:35:36 +0530 Subject: [PATCH 01/19] changes TextMagic to Textmagic in all places and uses email validator --- CHANGES.md | 2 +- app/config/variables.php | 2 +- app/controllers/api/messaging.php | 12 ++++++------ src/Appwrite/Platform/Workers/Messaging.php | 4 ++-- tests/e2e/Services/GraphQL/Base.php | 8 ++++---- tests/e2e/Services/GraphQL/MessagingTest.php | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 889f65e1e7..fbe1e548db 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -466,7 +466,7 @@ ## Features - Added Phone Authentication by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3357 - Added Twilio Support - - Added TextMagic Support + - Added Textmagic Support - Added Telesign Support - Added Endpoint to create Phone Session (`POST:/v1/account/sessions/phone`) - Added Endpoint to confirm Phone Session (`PUT:/v1/account/sessions/phone`) diff --git a/app/config/variables.php b/app/config/variables.php index 6f49d4a5da..c9329f6d55 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -440,7 +440,7 @@ return [ 'variables' => [ [ 'name' => '_APP_SMS_PROVIDER', - 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, textmagic, telesign, msg91, and vonage.", + 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, Textmagic, telesign, msg91, and vonage.", 'introduction' => '0.15.0', 'default' => '', 'required' => false, diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 35ba97c1af..536ebaf408 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -256,7 +256,7 @@ App::post('/v1/messaging/providers/telesign') }); App::post('/v1/messaging/providers/textmagic') - ->desc('Create TextMagic provider') + ->desc('Create Textmagic provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') ->label('audits.resource', 'provider/{response.$id}') @@ -264,7 +264,7 @@ App::post('/v1/messaging/providers/textmagic') ->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.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) @@ -689,7 +689,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->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 Text(256), 'Sender email address.', true) + ->param('from', '', new Email(), 'Sender email address.', true) ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) ->param('domain', '', new Text(0), 'Mailgun Domain.', true) ->inject('queueForEvents') @@ -764,7 +764,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) - ->param('from', '', new Text(256), 'Sender email address.', true) + ->param('from', '', new Email(), 'Sender email address.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') @@ -950,7 +950,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') }); App::patch('/v1/messaging/providers/textmagic/:providerId') - ->desc('Update TextMagic provider') + ->desc('Update Textmagic provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') ->label('audits.resource', 'provider/{response.$id}') @@ -958,7 +958,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'updateTextMagicProvider') + ->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) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 299ef4526f..0862f0b8fa 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -17,7 +17,7 @@ use Utopia\Messaging\Adapters\SMS as SMSAdapter; use Utopia\Messaging\Adapters\SMS\Mock; use Utopia\Messaging\Adapters\SMS\Msg91; use Utopia\Messaging\Adapters\SMS\Telesign; -use Utopia\Messaging\Adapters\SMS\TextMagic; +use Utopia\Messaging\Adapters\SMS\Textmagic; use Utopia\Messaging\Adapters\SMS\Twilio; use Utopia\Messaging\Adapters\SMS\Vonage; use Utopia\Messaging\Adapters\Push as PushAdapter; @@ -303,7 +303,7 @@ class Messaging extends Action return match ($provider->getAttribute('provider')) { 'mock' => new Mock('username', 'password'), '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']), 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']), 'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']), diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 8655395dc2..3777d67b73 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -1830,8 +1830,8 @@ trait Base } }'; case self::$CREATE_TEXTMAGIC_PROVIDER: - return 'mutation createTextMagicProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $apiKey: String!) { - messagingCreateTextMagicProvider(providerId: $providerId, name: $name, from: $from, username: $username, apiKey: $apiKey) { + return 'mutation createTextmagicProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $apiKey: String!) { + messagingCreateTextmagicProvider(providerId: $providerId, name: $name, from: $from, username: $username, apiKey: $apiKey) { _id name provider @@ -1944,8 +1944,8 @@ trait Base } }'; case self::$UPDATE_TEXTMAGIC_PROVIDER: - return 'mutation updateTextMagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) { - messagingUpdateTextMagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) { + return 'mutation updateTextmagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) { + messagingUpdateTextmagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) { _id name provider diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 27b2b52cd3..e5a4ca3a72 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -47,7 +47,7 @@ class MessagingTest extends Scope 'password' => 'my-password', 'from' => '+123456789', ], - 'TextMagic' => [ + 'Textmagic' => [ 'providerId' => ID::unique(), 'name' => 'Textmagic1', 'username' => 'my-username', @@ -134,7 +134,7 @@ class MessagingTest extends Scope 'username' => 'my-username', 'password' => 'my-password', ], - 'TextMagic' => [ + 'Textmagic' => [ 'providerId' => $providers[4]['_id'], 'name' => 'Textmagic2', 'username' => 'my-username', From 3394218cb0164585094f21d922f8ee1ebc084728 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 21 Nov 2023 15:00:02 +0530 Subject: [PATCH 02/19] adds validation for phone and email identifier --- app/config/errors.php | 10 ++++++++++ app/controllers/api/users.php | 25 +++++++++++++++++++++++++ src/Appwrite/Extend/Exception.php | 2 ++ 3 files changed, 37 insertions(+) diff --git a/app/config/errors.php b/app/config/errors.php index f7c9e8e55f..3c0b67c76a 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -103,6 +103,16 @@ return [ 'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.', 'code' => 405, ], + Exception::GENERAL_INVALID_EMAIL => [ + 'name' => Exception::GENERAL_INVALID_EMAIL, + 'description' => 'Value must be a valid email address.', + 'code' => 400, + ], + Exception::GENERAL_INVALID_PHONE => [ + 'name' => Exception::GENERAL_INVALID_PHONE, + 'description' => 'Value must be a valid phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', + 'code' => 400, + ], /** User Errors */ Exception::USER_COUNT_EXCEEDED => [ diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d9968c83a7..992a28ba8e 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -415,6 +415,18 @@ App::post('/v1/users/:userId/targets') } } + if ($providerType === 'email') { + $validator = new Email(); + if (!$validator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_EMAIL); + } + } elseif ($providerType === 'sms') { + $validator = new Phone(); + if (!$validator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_PHONE); + } + } + $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { @@ -1270,6 +1282,19 @@ App::patch('/v1/users/:userId/targets/:targetId') } if ($identifier) { + $providerType = $target->getAttribute('providerType'); + if ($providerType === 'email') { + $emailValidator = new Email(); + if (!$emailValidator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_EMAIL); + } + } elseif ($providerType === 'sms') { + $phoneValidator = new Phone(); + if (!$phoneValidator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_PHONE); + } + } + $target->setAttribute('identifier', $identifier); } diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 3869444752..61ea597941 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -55,6 +55,8 @@ class Exception extends \Exception public const GENERAL_CODES_DISABLED = 'general_codes_disabled'; public const GENERAL_USAGE_DISABLED = 'general_usage_disabled'; public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented'; + public const GENERAL_INVALID_EMAIL = 'general_invalid_email'; + public const GENERAL_INVALID_PHONE = 'general_invalid_phone'; /** Users */ public const USER_COUNT_EXCEEDED = 'user_count_exceeded'; From 88f228c106889805aa91f7087edfdb7a3a69ed5d Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 21 Nov 2023 15:11:09 +0530 Subject: [PATCH 03/19] review changes --- app/controllers/api/users.php | 51 +++++++++++-------- tests/e2e/Services/GraphQL/MessagingTest.php | 4 +- .../e2e/Services/Messaging/MessagingBase.php | 2 +- tests/e2e/Services/Users/UsersBase.php | 8 +-- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 992a28ba8e..c339b77fed 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -415,16 +415,21 @@ App::post('/v1/users/:userId/targets') } } - if ($providerType === 'email') { - $validator = new Email(); - if (!$validator->isValid($identifier)) { - throw new Exception(Exception::GENERAL_INVALID_EMAIL); - } - } elseif ($providerType === 'sms') { - $validator = new Phone(); - if (!$validator->isValid($identifier)) { - throw new Exception(Exception::GENERAL_INVALID_PHONE); - } + switch ($providerType) { + case 'email': + $validator = new Email(); + if (!$validator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_EMAIL); + } + break; + case 'sms': + $validator = new Phone(); + if (!$validator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_PHONE); + } + break; + default: + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } $user = $dbForProject->getDocument('users', $userId); @@ -1283,16 +1288,22 @@ App::patch('/v1/users/:userId/targets/:targetId') if ($identifier) { $providerType = $target->getAttribute('providerType'); - if ($providerType === 'email') { - $emailValidator = new Email(); - if (!$emailValidator->isValid($identifier)) { - throw new Exception(Exception::GENERAL_INVALID_EMAIL); - } - } elseif ($providerType === 'sms') { - $phoneValidator = new Phone(); - if (!$phoneValidator->isValid($identifier)) { - throw new Exception(Exception::GENERAL_INVALID_PHONE); - } + + switch ($providerType) { + case 'email': + $validator = new Email(); + if (!$validator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_EMAIL); + } + break; + case 'sms': + $validator = new Phone(); + if (!$validator->isValid($identifier)) { + throw new Exception(Exception::GENERAL_INVALID_PHONE); + } + break; + default: + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } $target->setAttribute('identifier', $identifier); diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index e5a4ca3a72..e3ca99f2ac 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -398,7 +398,7 @@ class MessagingTest extends Scope 'providerType' => 'email', 'userId' => $userId, 'providerId' => $providerId, - 'identifier' => 'token', + 'identifier' => 'random-email@mail.org', ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -409,7 +409,7 @@ class MessagingTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($userId, $response['body']['data']['usersCreateTarget']['userId']); - $this->assertEquals('token', $response['body']['data']['usersCreateTarget']['identifier']); + $this->assertEquals('random-email@mail.org', $response['body']['data']['usersCreateTarget']['identifier']); $targetId = $response['body']['data']['usersCreateTarget']['_id']; diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 1f09d95522..37a581b088 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -319,7 +319,7 @@ trait MessagingBase 'targetId' => ID::unique(), 'providerType' => 'email', 'providerId' => $provider['body']['$id'], - 'identifier' => 'my-token', + 'identifier' => 'random-email@mail.org', ]); $this->assertEquals(201, $target['headers']['status-code']); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index c9cb17d7b5..70230b8c8a 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -1245,11 +1245,11 @@ trait UsersBase 'targetId' => ID::unique(), 'providerId' => $provider['body']['$id'], 'providerType' => 'email', - 'identifier' => 'my-token', + 'identifier' => 'random-email@mail.org', ]); $this->assertEquals(201, $response['headers']['status-code']); $this->assertEquals($provider['body']['$id'], $response['body']['providerId']); - $this->assertEquals('my-token', $response['body']['identifier']); + $this->assertEquals('random-email@mail.org', $response['body']['identifier']); return $response['body']; } @@ -1262,10 +1262,10 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'identifier' => 'my-updated-token', + 'identifier' => 'random-email1@mail.org', ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('my-updated-token', $response['body']['identifier']); + $this->assertEquals('random-email1@mail.org', $response['body']['identifier']); return $response['body']; } From 9c6cd8512faf52438b2186f494b03d83f1976ffc Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 21 Nov 2023 15:19:19 +0530 Subject: [PATCH 04/19] review changes --- app/controllers/api/users.php | 4 ++++ tests/e2e/Services/GraphQL/UsersTest.php | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c339b77fed..0c1edb6a96 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -428,6 +428,8 @@ App::post('/v1/users/:userId/targets') throw new Exception(Exception::GENERAL_INVALID_PHONE); } break; + case 'push': + break; default: throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } @@ -1302,6 +1304,8 @@ App::patch('/v1/users/:userId/targets/:targetId') throw new Exception(Exception::GENERAL_INVALID_PHONE); } break; + case 'push': + break; default: throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } diff --git a/tests/e2e/Services/GraphQL/UsersTest.php b/tests/e2e/Services/GraphQL/UsersTest.php index 59c0c4a805..3b49337a63 100644 --- a/tests/e2e/Services/GraphQL/UsersTest.php +++ b/tests/e2e/Services/GraphQL/UsersTest.php @@ -80,7 +80,7 @@ class UsersTest extends Scope 'userId' => $user['_id'], 'providerType' => 'email', 'providerId' => $providerId, - 'identifier' => 'identifier', + 'identifier' => 'random-email@mail.org', ] ]; @@ -90,7 +90,7 @@ class UsersTest extends Scope ], $this->getHeaders()), $graphQLPayload); $this->assertEquals(200, $target['headers']['status-code']); - $this->assertEquals('identifier', $target['body']['data']['usersCreateTarget']['identifier']); + $this->assertEquals('random-email@mail.org', $target['body']['data']['usersCreateTarget']['identifier']); return $target['body']['data']['usersCreateTarget']; } @@ -271,7 +271,7 @@ class UsersTest extends Scope ], $this->getHeaders()), $graphQLPayload); $this->assertEquals(200, $target['headers']['status-code']); - $this->assertEquals('identifier', $target['body']['data']['usersGetTarget']['identifier']); + $this->assertEquals('random-email@mail.org', $target['body']['data']['usersGetTarget']['identifier']); } public function testUpdateUserStatus() @@ -470,7 +470,7 @@ class UsersTest extends Scope 'variables' => [ 'userId' => $target['userId'], 'targetId' => $target['_id'], - 'identifier' => 'newidentifier', + 'identifier' => 'random-email1@mail.org', ], ]; @@ -480,7 +480,7 @@ class UsersTest extends Scope ], $this->getHeaders()), $graphQLPayload); $this->assertEquals(200, $target['headers']['status-code']); - $this->assertEquals('newidentifier', $target['body']['data']['usersUpdateTarget']['identifier']); + $this->assertEquals('random-email1@mail.org', $target['body']['data']['usersUpdateTarget']['identifier']); } public function testDeleteUserSessions() From d9826cdce8ff8f943a1c9866aca99223b656c814 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Thu, 23 Nov 2023 00:39:55 +0530 Subject: [PATCH 05/19] makes provider creation fields optional and adds target info in subscriber model --- app/controllers/api/messaging.php | 583 +++++++++++++----- src/Appwrite/Platform/Workers/Messaging.php | 6 +- .../Utopia/Response/Model/Subscriber.php | 12 + .../e2e/Services/Messaging/MessagingBase.php | 8 +- 4 files changed, 466 insertions(+), 143 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 536ebaf408..282cb32108 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -54,31 +54,57 @@ App::post('/v1/messaging/providers/mailgun') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Email(), 'Sender email address.') - ->param('apiKey', '', new Text(0), 'Mailgun API Key.') - ->param('domain', '', new Text(0), 'Mailgun Domain.') - ->param('isEuRegion', false, new Boolean(), 'Set as EU region.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Email(), 'Sender email address.', true) + ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) + ->param('domain', '', new Text(0), 'Mailgun Domain.', true) + ->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $apiKey, string $domain, bool $isEuRegion, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $apiKey, string $domain, ?bool $isEuRegion, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if ($isEuRegion === true || $isEuRegion === false) { + $credentials['isEuRegion'] = $isEuRegion; + } + + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; + } + + if (!empty($domain)) { + $credentials['domain'] = $domain; + } + + if ( + $enabled === true && + \array_key_exists('isEuRegion', $credentials) && + \array_key_exists('apiKey', $credentials) && + \array_key_exists('domain', $credentials) && + \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'mailgun', 'type' => 'email', 'enabled' => $enabled, - 'credentials' => [ - 'apiKey' => $apiKey, - 'domain' => $domain, - 'isEuRegion' => $isEuRegion, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $options, ]); try { @@ -111,26 +137,45 @@ App::post('/v1/messaging/providers/sendgrid') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Email(), 'Sender email address.') - ->param('apiKey', '', new Text(0), 'Sendgrid API key.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Email(), 'Sender email address.', true) + ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $apiKey, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $apiKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; + } + + if ( + $enabled === true + && \array_key_exists('apiKey', $credentials) + && \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'sendgrid', 'type' => 'email', 'enabled' => $enabled, - 'credentials' => [ - 'apiKey' => $apiKey, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $options, ]); try { @@ -163,28 +208,51 @@ App::post('/v1/messaging/providers/msg91') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') - ->param('senderId', '', new Text(0), 'Msg91 Sender ID.') - ->param('authKey', '', new Text(0), 'Msg91 Auth Key.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->param('senderId', '', new Text(0), 'Msg91 Sender ID.', true) + ->param('authKey', '', new Text(0), 'Msg91 Auth Key.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $senderId, string $authKey, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $senderId, string $authKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if (!empty($senderId)) { + $credentials['senderId'] = $senderId; + } + + if (!empty($authKey)) { + $credentials['authKey'] = $authKey; + } + + if ( + $enabled === true + && \array_key_exists('senderId', $credentials) + && \array_key_exists('authKey', $credentials) + && \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'msg91', 'type' => 'sms', 'enabled' => $enabled, - 'credentials' => [ - 'senderId' => $senderId, - 'authKey' => $authKey, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $options, ]); try { @@ -217,28 +285,51 @@ App::post('/v1/messaging/providers/telesign') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') - ->param('username', '', new Text(0), 'Telesign username.') - ->param('password', '', new Text(0), 'Telesign password.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->param('username', '', new Text(0), 'Telesign username.', true) + ->param('password', '', new Text(0), 'Telesign password.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $username, string $password, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $username, string $password, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if (!empty($username)) { + $credentials['username'] = $username; + } + + if (!empty($password)) { + $credentials['password'] = $password; + } + + if ( + $enabled === true + && \array_key_exists('username', $credentials) + && \array_key_exists('password', $credentials) + && \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'telesign', 'type' => 'sms', 'enabled' => $enabled, - 'credentials' => [ - 'username' => $username, - 'password' => $password, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $options, ]); try { @@ -271,28 +362,51 @@ App::post('/v1/messaging/providers/textmagic') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') - ->param('username', '', new Text(0), 'Textmagic username.') - ->param('apiKey', '', new Text(0), 'Textmagic apiKey.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->param('username', '', new Text(0), 'Textmagic username.', true) + ->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $username, string $apiKey, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $username, string $apiKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if (!empty($username)) { + $credentials['username'] = $username; + } + + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; + } + + if ( + $enabled === true + && \array_key_exists('username', $credentials) + && \array_key_exists('apiKey', $credentials) + && \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'textmagic', 'type' => 'sms', 'enabled' => $enabled, - 'credentials' => [ - 'username' => $username, - 'apiKey' => $apiKey, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $options, ]); try { @@ -325,28 +439,51 @@ App::post('/v1/messaging/providers/twilio') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') - ->param('accountSid', '', new Text(0), 'Twilio account secret ID.') - ->param('authToken', '', new Text(0), 'Twilio authentication token.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true) + ->param('authToken', '', new Text(0), 'Twilio authentication token.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $accountSid, string $authToken, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $accountSid, string $authToken, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if (!empty($accountSid)) { + $credentials['accountSid'] = $accountSid; + } + + if (!empty($authToken)) { + $credentials['authToken'] = $authToken; + } + + if ( + $enabled === true + && \array_key_exists('accountSid', $credentials) + && \array_key_exists('authToken', $credentials) + && \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'twilio', 'type' => 'sms', 'enabled' => $enabled, - 'credentials' => [ - 'accountSid' => $accountSid, - 'authToken' => $authToken, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $from, ]); try { @@ -379,28 +516,51 @@ App::post('/v1/messaging/providers/vonage') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') - ->param('apiKey', '', new Text(0), 'Vonage API key.') - ->param('apiSecret', '', new Text(0), 'Vonage API secret.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->param('apiKey', '', new Text(0), 'Vonage API key.', true) + ->param('apiSecret', '', new Text(0), 'Vonage API secret.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $from, string $apiKey, string $apiSecret, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $from, string $apiKey, string $apiSecret, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $options = []; + + if (!empty($from)) { + $options ['from'] = $from; + } + + $credentials = []; + + if (!empty($apiKey)) { + $credentials['apiKey'] = $apiKey; + } + + if (!empty($apiSecret)) { + $credentials['apiSecret'] = $apiSecret; + } + + if ( + $enabled === true + && \array_key_exists('apiKey', $credentials) + && \array_key_exists('apiSecret', $credentials) + && \array_key_exists('from', $options) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'vonage', 'type' => 'sms', 'enabled' => $enabled, - 'credentials' => [ - 'apiKey' => $apiKey, - 'apiSecret' => $apiSecret, - ], - 'options' => [ - 'from' => $from, - ] + 'credentials' => $credentials, + 'options' => $options, ]); try { @@ -433,22 +593,33 @@ App::post('/v1/messaging/providers/fcm') ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') - ->param('serverKey', '', new Text(0), 'FCM server key.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('serverKey', '', new Text(0), 'FCM server key.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $serverKey, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $serverKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $credentials = []; + + if (!empty($serverKey)) { + $credentials['serverKey'] = $serverKey; + } + + if ($enabled === true && \array_key_exists('serverKey', $credentials)) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'fcm', 'type' => 'push', 'enabled' => $enabled, - 'credentials' => [ - 'serverKey' => $serverKey, - ], + 'credentials' => $credentials ]); try { @@ -481,30 +652,60 @@ App::post('/v1/messaging/providers/apns') ->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('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.') - ->param('enabled', true, new Boolean(), 'Set as enabled.', true) + ->param('authKey', '', new Text(0), 'APNS authentication key.', true) + ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true) + ->param('teamId', '', new Text(0), 'APNS team ID.', true) + ->param('bundleId', '', new Text(0), 'APNS bundle ID.', true) + ->param('endpoint', '', new Text(0), 'APNS endpoint.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $credentials = []; + + if (!empty($authKey)) { + $credentials['authKey'] = $authKey; + } + + if (!empty($authKeyId)) { + $credentials['authKeyId'] = $authKeyId; + } + + if (!empty($teamId)) { + $credentials['teamId'] = $teamId; + } + + if (!empty($bundleId)) { + $credentials['bundleId'] = $bundleId; + } + + if (!empty($endpoint)) { + $credentials['endpoint'] = $endpoint; + } + + if ( + $enabled === true + && \array_key_exists('authKey', $credentials) + && \array_key_exists('authKeyId', $credentials) + && \array_key_exists('teamId', $credentials) + && \array_key_exists('bundleId', $credentials) + && \array_key_exists('endpoint', $credentials) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider = new Document([ '$id' => $providerId, 'name' => $name, 'provider' => 'apns', 'type' => 'push', 'enabled' => $enabled, - 'credentials' => [ - 'authKey' => $authKey, - 'authKeyId' => $authKeyId, - 'teamId' => $teamId, - 'bundleId' => $bundleId, - 'endpoint' => $endpoint, - ], + 'credentials' => $credentials, ]); try { @@ -717,10 +918,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if ($isEuRegion === true || $isEuRegion === false) { @@ -737,6 +934,21 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true && + \array_key_exists('isEuRegion', $credentials) && + \array_key_exists('apiKey', $credentials) && + \array_key_exists('domain', $credentials) && + \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -790,16 +1002,25 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if (!empty($apiKey)) { $provider->setAttribute('credentials', [ 'apiKey' => $apiKey, ]); } + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('apiKey', $provider->getAttribute('credentials')) + && \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -854,10 +1075,6 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if (!empty($senderId)) { @@ -870,6 +1087,20 @@ App::patch('/v1/messaging/providers/msg91/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('senderId', $credentials) + && \array_key_exists('authKey', $credentials) + && \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -924,10 +1155,6 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if (!empty($username)) { @@ -940,6 +1167,21 @@ App::patch('/v1/messaging/providers/telesign/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('username', $credentials) + && \array_key_exists('password', $credentials) + && \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -994,10 +1236,6 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if (!empty($username)) { @@ -1010,6 +1248,21 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('username', $credentials) + && \array_key_exists('apiKey', $credentials) + && \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -1064,10 +1317,6 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if (!empty($accountSid)) { @@ -1080,6 +1329,21 @@ App::patch('/v1/messaging/providers/twilio/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('accountSid', $credentials) + && \array_key_exists('authToken', $credentials) + && \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -1134,10 +1398,6 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ]); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if (!empty($apiKey)) { @@ -1150,6 +1410,21 @@ App::patch('/v1/messaging/providers/vonage/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('apiKey', $credentials) + && \array_key_exists('apiSecret', $credentials) + && \array_key_exists('from', $provider->getAttribute('options')) + ) { + $enabled = true; + } else { + $enabled = false; + } + + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -1196,14 +1471,20 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider->setAttribute('name', $name); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - if (!empty($serverKey)) { $provider->setAttribute('credentials', ['serverKey' => $serverKey]); } + if ($enabled === true || $enabled === false) { + if ($enabled === true && \array_key_exists('serverKey', $provider->getAttribute('credentials'))) { + $enabled = true; + } else { + $enabled = false; + } + + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -1255,10 +1536,6 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('name', $name); } - if ($enabled === true || $enabled === false) { - $provider->setAttribute('enabled', $enabled); - } - $credentials = $provider->getAttribute('credentials'); if (!empty($authKey)) { @@ -1283,6 +1560,23 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('credentials', $credentials); + if ($enabled === true || $enabled === false) { + if ( + $enabled === true + && \array_key_exists('authKey', $credentials) + && \array_key_exists('authKeyId', $credentials) + && \array_key_exists('teamId', $credentials) + && \array_key_exists('bundleId', $credentials) + && \array_key_exists('endpoint', $credentials) + ) { + $enabled = true; + } else { + $enabled = false; + } + + $provider->setAttribute('enabled', $enabled); + } + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); $queueForEvents @@ -1726,8 +2020,11 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { return function () use ($subscriber, $dbForProject) { $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); return $subscriber + ->setAttribute('identifier', $target->getAttribute('identifier')) + ->setAttribute('providerType', $target->getAttribute('providerType')) ->setAttribute('userName', $user->getAttribute('name')); }; }, $subscribers)); @@ -1852,8 +2149,12 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') } $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); - $subscriber->setAttribute('userName', $user->getAttribute('name')); + $subscriber + ->setAttribute('identifier', $target->getAttribute('identifier')) + ->setAttribute('providerType', $target->getAttribute('providerType')) + ->setAttribute('userName', $user->getAttribute('name')); $response ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 0862f0b8fa..8488d8a9e4 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -144,7 +144,11 @@ class Messaging extends Action if ($internalProvider->getId() === $providerId) { $provider = $internalProvider; } else { - $provider = $dbForProject->getDocument('providers', $providerId); + $provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]); + + if ($provider->isEmpty()) { + $provider = $internalProvider; + } } $providers[] = $provider; diff --git a/src/Appwrite/Utopia/Response/Model/Subscriber.php b/src/Appwrite/Utopia/Response/Model/Subscriber.php index 5080b44333..dfacc0e46e 100644 --- a/src/Appwrite/Utopia/Response/Model/Subscriber.php +++ b/src/Appwrite/Utopia/Response/Model/Subscriber.php @@ -34,6 +34,18 @@ class Subscriber extends Model 'default' => '', 'example' => '259125845563242502', ]) + ->addRule('providerType', [ + 'type' => self::TYPE_STRING, + 'description' => 'Target\'s provider type.', + 'default' => '', + 'example' => 'email', + ]) + ->addRule('identifier', [ + 'type' => self::TYPE_STRING, + 'description' => 'Target identifier.', + 'default' => '', + 'example' => 'random-email@mail.org', + ]) ->addRule('userId', [ 'type' => self::TYPE_STRING, 'description' => 'User ID.', diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 37a581b088..1bd91d9222 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -350,7 +350,9 @@ trait MessagingBase 'topicId' => $topic['body']['$id'], 'targetId' => $target['body']['$id'], 'userId' => $target['body']['userId'], - 'subscriberId' => $response['body']['$id'] + 'subscriberId' => $response['body']['$id'], + 'identifier' => $target['body']['identifier'], + 'providerType' => $target['body']['providerType'], ]; } @@ -369,6 +371,8 @@ trait MessagingBase $this->assertEquals($data['topicId'], $response['body']['topicId']); $this->assertEquals($data['targetId'], $response['body']['targetId']); $this->assertEquals($data['userId'], $response['body']['userId']); + $this->assertEquals($data['providerType'], $response['body']['providerType']); + $this->assertEquals($data['identifier'], $response['body']['identifier']); } /** @@ -385,6 +389,8 @@ trait MessagingBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, $response['body']['total']); $this->assertEquals($data['userId'], $response['body']['subscribers'][0]['userId']); + $this->assertEquals($data['providerType'], $response['body']['subscribers'][0]['providerType']); + $this->assertEquals($data['identifier'], $response['body']['subscribers'][0]['identifier']); $this->assertEquals(\count($response['body']['subscribers']), $response['body']['total']); return $data; From 858175fe0025716c72ced2b5c7aae45ead9e30ea Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 23 Nov 2023 14:35:16 +0530 Subject: [PATCH 06/19] adds target object in subscriber model --- app/config/collections.php | 36 ------------------- app/controllers/api/messaging.php | 18 +++++----- .../Utopia/Response/Model/Subscriber.php | 31 ++++++++-------- .../e2e/Services/Messaging/MessagingBase.php | 15 ++++---- 4 files changed, 30 insertions(+), 70 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index f310c91211..7e715baa2c 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1722,28 +1722,6 @@ $commonCollections = [ '$id' => ID::custom('subscribers'), 'name' => 'Subscribers', 'attributes' => [ - [ - '$id' => ID::custom('userId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('userInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], [ '$id' => ID::custom('targetId'), 'type' => Database::VAR_STRING, @@ -1790,20 +1768,6 @@ $commonCollections = [ ], ], 'indexes' => [ - [ - '$id' => ID::custom('_key_userId'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['userId'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => ID::custom('_key_userInternalId'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['userInternalId'], - 'lengths' => [], - 'orders' => [], - ], [ '$id' => ID::custom('_key_targetId'), 'type' => Database::INDEX_KEY, diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 282cb32108..f13b037b02 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1946,10 +1946,8 @@ App::post('/v1/messaging/topics/:topicId/subscribers') '$id' => $subscriberId, '$permissions' => [ Permission::read(Role::user($user->getId())), - Permission::delete(Role::user($target->getAttribute('userId'))), + Permission::delete(Role::user($user->getId())), ], - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), 'topicId' => $topicId, 'topicInternalId' => $topic->getInternalId(), 'targetId' => $targetId, @@ -1967,7 +1965,9 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->setParam('topicId', $topic->getId()) ->setParam('subscriberId', $subscriber->getId()); - $subscriber->setAttribute('userName', $user->getAttribute('name')); + $subscriber + ->setAttribute('target', $target) + ->setAttribute('userName', $user->getAttribute('name')); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -2019,12 +2019,11 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { return function () use ($subscriber, $dbForProject) { - $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); return $subscriber - ->setAttribute('identifier', $target->getAttribute('identifier')) - ->setAttribute('providerType', $target->getAttribute('providerType')) + ->setAttribute('target', $target) ->setAttribute('userName', $user->getAttribute('name')); }; }, $subscribers)); @@ -2148,12 +2147,11 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); } - $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $subscriber->getAttribute('userId'))); $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $subscriber - ->setAttribute('identifier', $target->getAttribute('identifier')) - ->setAttribute('providerType', $target->getAttribute('providerType')) + ->setAttribute('target', $target) ->setAttribute('userName', $user->getAttribute('name')); $response diff --git a/src/Appwrite/Utopia/Response/Model/Subscriber.php b/src/Appwrite/Utopia/Response/Model/Subscriber.php index dfacc0e46e..e699df876c 100644 --- a/src/Appwrite/Utopia/Response/Model/Subscriber.php +++ b/src/Appwrite/Utopia/Response/Model/Subscriber.php @@ -34,23 +34,20 @@ class Subscriber extends Model 'default' => '', 'example' => '259125845563242502', ]) - ->addRule('providerType', [ - 'type' => self::TYPE_STRING, - 'description' => 'Target\'s provider type.', - 'default' => '', - 'example' => 'email', - ]) - ->addRule('identifier', [ - 'type' => self::TYPE_STRING, - 'description' => 'Target identifier.', - 'default' => '', - 'example' => 'random-email@mail.org', - ]) - ->addRule('userId', [ - 'type' => self::TYPE_STRING, - 'description' => 'User ID.', - 'default' => '', - 'example' => '5e5ea5c16897e', + ->addRule('target', [ + 'type' => Response::MODEL_TARGET, + 'description' => 'Target.', + 'default' => [], + 'example' => [ + '$id' => '259125845563242502', + '$createdAt' => self::TYPE_DATETIME_EXAMPLE, + '$updatedAt' => self::TYPE_DATETIME_EXAMPLE, + 'providerType' => 'email', + 'providerId' => '259125845563242502', + 'name' => 'ageon-app-email', + 'identifier' => 'random-mail@email.org', + 'userId' => '5e5ea5c16897e', + ], ]) ->addRule('userName', [ 'type' => self::TYPE_STRING, diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 1bd91d9222..690e503e77 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -333,7 +333,8 @@ trait MessagingBase ]); $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals($target['body']['userId'], $response['body']['userId']); + $this->assertEquals($target['body']['userId'], $response['body']['target']['userId']); + $this->assertEquals($target['body']['providerType'], $response['body']['target']['providerType']); $topic = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['$id'], [ 'content-type' => 'application/json', @@ -370,9 +371,9 @@ trait MessagingBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($data['topicId'], $response['body']['topicId']); $this->assertEquals($data['targetId'], $response['body']['targetId']); - $this->assertEquals($data['userId'], $response['body']['userId']); - $this->assertEquals($data['providerType'], $response['body']['providerType']); - $this->assertEquals($data['identifier'], $response['body']['identifier']); + $this->assertEquals($data['userId'], $response['body']['target']['userId']); + $this->assertEquals($data['providerType'], $response['body']['target']['providerType']); + $this->assertEquals($data['identifier'], $response['body']['target']['identifier']); } /** @@ -388,9 +389,9 @@ trait MessagingBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, $response['body']['total']); - $this->assertEquals($data['userId'], $response['body']['subscribers'][0]['userId']); - $this->assertEquals($data['providerType'], $response['body']['subscribers'][0]['providerType']); - $this->assertEquals($data['identifier'], $response['body']['subscribers'][0]['identifier']); + $this->assertEquals($data['userId'], $response['body']['subscribers'][0]['target']['userId']); + $this->assertEquals($data['providerType'], $response['body']['subscribers'][0]['target']['providerType']); + $this->assertEquals($data['identifier'], $response['body']['subscribers'][0]['target']['identifier']); $this->assertEquals(\count($response['body']['subscribers']), $response['body']['total']); return $data; From e3f60f4c78023858f8dabcad86bdb2518d229ca5 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 23 Nov 2023 23:14:02 +0530 Subject: [PATCH 07/19] fix tests --- .env | 2 +- src/Appwrite/Utopia/Response/Model/Target.php | 6 +++++ tests/e2e/Services/GraphQL/Base.php | 27 ++++++++++++++++--- tests/e2e/Services/GraphQL/MessagingTest.php | 10 +++---- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.env b/.env index 6ea21eed9d..f77083a035 100644 --- a/.env +++ b/.env @@ -87,7 +87,7 @@ _APP_LOGGING_PROVIDER= _APP_LOGGING_CONFIG= _APP_GRAPHQL_MAX_BATCH_SIZE=10 _APP_GRAPHQL_MAX_COMPLEXITY=250 -_APP_GRAPHQL_MAX_DEPTH=3 +_APP_GRAPHQL_MAX_DEPTH=4 _APP_DOCKER_HUB_USERNAME= _APP_DOCKER_HUB_PASSWORD= _APP_VCS_GITHUB_APP_NAME= diff --git a/src/Appwrite/Utopia/Response/Model/Target.php b/src/Appwrite/Utopia/Response/Model/Target.php index f6346f409a..a92d1b34ca 100644 --- a/src/Appwrite/Utopia/Response/Model/Target.php +++ b/src/Appwrite/Utopia/Response/Model/Target.php @@ -28,6 +28,12 @@ class Target extends Model 'default' => '', 'example' => self::TYPE_DATETIME_EXAMPLE, ]) + ->addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Target Name.', + 'default' => '', + 'example' => 'Aegon apple token', + ]) ->addRule('userId', [ 'type' => self::TYPE_STRING, 'description' => 'User ID.', diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 3777d67b73..6939e6a9b5 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -2044,9 +2044,16 @@ trait Base return 'mutation createSubscriber($subscriberId: String!, $targetId: String!, $topicId: String!) { messagingCreateSubscriber(subscriberId: $subscriberId, targetId: $targetId, topicId: $topicId) { _id - userId targetId topicId + userName + target { + _id + userId + name + providerType + identifier + } } }'; case self::$LIST_SUBSCRIBERS: @@ -2055,9 +2062,16 @@ trait Base total subscribers { _id - userId targetId topicId + userName + target { + _id + userId + name + providerType + identifier + } } } }'; @@ -2065,9 +2079,16 @@ trait Base return 'query getSubscriber($topicId: String!, $subscriberId: String!) { messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) { _id - userId targetId topicId + userName + target { + _id + userId + name + providerType + identifier + } } }'; case self::$DELETE_SUBSCRIBER: diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index e3ca99f2ac..d1a084cfc1 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -430,7 +430,7 @@ class MessagingTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['topicId'], $topicId); $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['targetId'], $targetId); - $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['userId'], $userId); + $this->assertEquals($response['body']['data']['messagingCreateSubscriber']['target']['userId'], $userId); return $response['body']['data']['messagingCreateSubscriber']; } @@ -454,9 +454,9 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['topicId'], $subscriber['topicId']); - $this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['targetId'], $subscriber['targetId']); - $this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['userId'], $subscriber['userId']); + $this->assertEquals($subscriber['topicId'], $response['body']['data']['messagingListSubscribers']['subscribers'][0]['topicId']); + $this->assertEquals($subscriber['targetId'], $response['body']['data']['messagingListSubscribers']['subscribers'][0]['targetId']); + $this->assertEquals($subscriber['target']['userId'], $response['body']['data']['messagingListSubscribers']['subscribers'][0]['target']['userId']); $this->assertEquals(1, \count($response['body']['data']['messagingListSubscribers']['subscribers'])); } @@ -487,7 +487,7 @@ class MessagingTest extends Scope $this->assertEquals($subscriberId, $response['body']['data']['messagingGetSubscriber']['_id']); $this->assertEquals($topicId, $response['body']['data']['messagingGetSubscriber']['topicId']); $this->assertEquals($subscriber['targetId'], $response['body']['data']['messagingGetSubscriber']['targetId']); - $this->assertEquals($subscriber['userId'], $response['body']['data']['messagingGetSubscriber']['userId']); + $this->assertEquals($subscriber['target']['userId'], $response['body']['data']['messagingGetSubscriber']['target']['userId']); } /** From ee4c2d0e0dfcb9115f595458ab381dce49478572 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 28 Nov 2023 18:42:34 +0530 Subject: [PATCH 08/19] adds target when creating user via server endpoint --- app/controllers/api/account.php | 89 +++++++++++++-------- app/controllers/api/users.php | 89 ++++++++++++++++++++- src/Appwrite/Platform/Workers/Messaging.php | 24 +++--- tests/e2e/Services/GraphQL/UsersTest.php | 2 +- tests/e2e/Services/Users/UsersBase.php | 5 +- 5 files changed, 156 insertions(+), 53 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index eda29656fe..7034c93912 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -149,18 +149,20 @@ App::post('/v1/account') ]); $user->removeAttribute('$internalId'); $user = Authorization::skip(fn() => $dbForProject->createDocument('users', $user)); - $target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($userId)), - Permission::delete(Role::user($userId)), - ], - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerType' => 'email', - 'identifier' => $email, - ]))); - $user->setAttribute('targets', [$target]); + try { + $target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerType' => 'email', + 'identifier' => $email, + ]))); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]); + } catch (Duplicate) { + $existingTarget = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$email]), + ]); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate) { throw new Exception(Exception::USER_ALREADY_EXISTS); @@ -1309,6 +1311,21 @@ App::post('/v1/account/sessions/phone') $user->removeAttribute('$internalId'); Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + try { + $target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerType' => 'sms', + 'identifier' => $phone, + ]))); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]); + } catch (Duplicate) { + $existingTarget = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$phone]), + ]); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } + $dbForProject->deleteCachedDocument('users', $user->getId()); } $secret = Auth::codeGenerator(); @@ -2102,25 +2119,25 @@ App::patch('/v1/account/email') ->setAttribute('passwordUpdate', DateTime::now()); } - $target = $dbForProject->findOne('targets', [ + $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), - ]); + ])); - if ($target && !$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } - /** - * @var Document $oldTarget - */ - $oldTarget = $user->find('identifier', $oldEmail, 'targets'); - - if ($oldTarget !== false && !$oldTarget->isEmpty()) { - $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); - } - try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldEmail, 'targets'); + + if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { + Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); + } + $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -2165,18 +2182,15 @@ App::patch('/v1/account/phone') throw new Exception(Exception::USER_INVALID_CREDENTIALS); } - $target = $dbForProject->findOne('targets', [ + $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), - ]); + ])); - if ($target && !$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } - /** - * @var Document $oldTarget - */ - $oldTarget = $user->find('identifier', $user->getAttribute('phone'), 'targets'); + $oldPhone = $user->getAttribute('phone'); $user ->setAttribute('phone', $phone) @@ -2191,12 +2205,17 @@ App::patch('/v1/account/phone') ->setAttribute('passwordUpdate', DateTime::now()); } - if ($oldTarget !== false && !$oldTarget->isEmpty()) { - $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)); - } - try { $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldPhone, 'targets'); + + if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { + Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); + } + $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 0c1edb6a96..0b099cb8d3 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -99,6 +99,42 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'memberships' => null, 'search' => implode(' ', [$userId, $email, $phone, $name]), ])); + + if ($email) { + try { + $target = $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerType' => 'email', + 'identifier' => $email, + ])); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]); + } catch (Duplicate) { + $existingTarget = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$email]), + ]); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } + } + + if ($phone) { + try { + $target = $dbForProject->createDocument('targets', new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerType' => 'sms', + 'identifier' => $phone, + ])); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]); + } catch (Duplicate) { + $existingTarget = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$phone]), + ]); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + } + } + + $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -399,10 +435,11 @@ App::post('/v1/users/:userId/targets') ->param('providerType', '', new WhiteList(['email', 'sms', 'push']), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.') ->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)') ->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true) + ->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true) ->inject('queueForEvents') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, Event $queueForEvents, Response $response, Database $dbForProject) { + ->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; $provider = new Document(); @@ -455,6 +492,7 @@ App::post('/v1/users/:userId/targets') 'userId' => $userId, 'userInternalId' => $user->getInternalId(), 'identifier' => $identifier, + 'name' => ($name !== '') ? $name : null, ])); } catch (Duplicate) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); @@ -801,7 +839,7 @@ App::get('/v1/users/:userId/targets') if ($cursor) { $targetId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $cursorDocument = $dbForProject->getDocument('targets', $targetId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Target '{$targetId}' for the 'cursor' value not found."); @@ -1119,6 +1157,16 @@ App::patch('/v1/users/:userId/email') throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } + $target = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$email]), + ]); + + if ($target && !$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + + $oldEmail = $user->getAttribute('email'); + $user ->setAttribute('email', $email) ->setAttribute('emailVerification', false) @@ -1127,6 +1175,15 @@ App::patch('/v1/users/:userId/email') try { $user = $dbForProject->updateDocument('users', $user->getId(), $user); + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldEmail, 'targets'); + + if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); + } + $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -1164,13 +1221,32 @@ App::patch('/v1/users/:userId/phone') throw new Exception(Exception::USER_NOT_FOUND); } + $oldPhone = $user->getAttribute('phone'); + $user ->setAttribute('phone', $number) ->setAttribute('phoneVerification', false) ; + $target = $dbForProject->findOne('targets', [ + Query::equal('identifier', [$number]), + ]); + + if ($target && !$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + try { $user = $dbForProject->updateDocument('users', $user->getId(), $user); + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldPhone, 'targets'); + + if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { + $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number)); + } + $dbForProject->deleteCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS); } @@ -1266,12 +1342,13 @@ App::patch('/v1/users/:userId/targets/:targetId') ->label('sdk.response.model', Response::MODEL_TARGET) ->param('userId', '', new UID(), 'User ID.') ->param('targetId', '', new UID(), 'Target ID.') - ->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true) ->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true) + ->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true) + ->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true) ->inject('queueForEvents') ->inject('response') ->inject('dbForProject') - ->action(function (string $userId, string $targetId, string $providerId, string $identifier, Event $queueForEvents, Response $response, Database $dbForProject) { + ->action(function (string $userId, string $targetId, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) { $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { @@ -1324,6 +1401,10 @@ App::patch('/v1/users/:userId/targets/:targetId') $target->setAttribute('providerInternalId', $provider->getInternalId()); } + if ($name) { + $target->setAttribute('name', $name); + } + $target = $dbForProject->updateDocument('targets', $target->getId(), $target); $dbForProject->deleteCachedDocument('users', $user->getId()); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 8488d8a9e4..0414bbaf59 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -107,7 +107,7 @@ class Messaging extends Action $recipients = \array_merge($recipients, $targets); } - $internalProvider = $dbForProject->findOne('providers', [ + $primaryProvider = $dbForProject->findOne('providers', [ Query::equal('enabled', [true]), Query::equal('type', [$recipients[0]->getAttribute('providerType')]), ]); @@ -124,30 +124,32 @@ class Messaging extends Action foreach ($recipients as $recipient) { $providerId = $recipient->getAttribute('providerId'); - if (!$providerId) { - $providerId = $internalProvider->getId(); + if (!$providerId && $primaryProvider instanceof Document && !$primaryProvider->isEmpty()) { + $providerId = $primaryProvider->getId(); } - if (!isset($identifiersByProviderId[$providerId])) { - $identifiersByProviderId[$providerId] = []; + if ($providerId) { + if (!isset($identifiersByProviderId[$providerId])) { + $identifiersByProviderId[$providerId] = []; + } + $identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier'); } - $identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier'); } /** * @var array[] $results */ - $results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $internalProvider, $message, $dbForProject) { - return function () use ($providerId, $identifiersByProviderId, $providers, $internalProvider, $message, $dbForProject) { + $results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) { + return function () use ($providerId, $identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) { $provider = new Document(); - if ($internalProvider->getId() === $providerId) { - $provider = $internalProvider; + if ($primaryProvider->getId() === $providerId) { + $provider = $primaryProvider; } else { $provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]); if ($provider->isEmpty()) { - $provider = $internalProvider; + $provider = $primaryProvider; } } diff --git a/tests/e2e/Services/GraphQL/UsersTest.php b/tests/e2e/Services/GraphQL/UsersTest.php index 3b49337a63..1dac9123a9 100644 --- a/tests/e2e/Services/GraphQL/UsersTest.php +++ b/tests/e2e/Services/GraphQL/UsersTest.php @@ -247,7 +247,7 @@ class UsersTest extends Scope $this->assertEquals(200, $targets['headers']['status-code']); $this->assertIsArray($targets['body']['data']['usersListTargets']); - $this->assertCount(1, $targets['body']['data']['usersListTargets']['targets']); + $this->assertCount(2, $targets['body']['data']['usersListTargets']['targets']); } /** diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 70230b8c8a..7d22f23251 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -23,6 +23,7 @@ trait UsersBase 'password' => 'password', 'name' => 'Cristiano Ronaldo', ], false); + $this->assertEquals($user['headers']['status-code'], 201); // Test empty prefs is object not array $bodyString = $user['body']; @@ -1279,7 +1280,7 @@ trait UsersBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, \count($response['body']['targets'])); + $this->assertEquals(2, \count($response['body']['targets'])); } /** @@ -1313,7 +1314,7 @@ trait UsersBase ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(0, $response['body']['total']); + $this->assertEquals(1, $response['body']['total']); } /** From 4bc23afc6586ab94a9733ea0e2816efc1e9fa856 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 29 Nov 2023 17:05:37 +1300 Subject: [PATCH 09/19] Add constants for message types --- app/controllers/api/account.php | 6 +++--- app/controllers/api/users.php | 12 ++++++------ app/init.php | 10 +++++++--- src/Appwrite/Platform/Workers/Messaging.php | 16 ++++++++-------- src/Appwrite/Utopia/Response/Model/Provider.php | 2 +- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index eda29656fe..6a06e2a09e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -157,7 +157,7 @@ App::post('/v1/account') ], 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'providerType' => 'email', + 'providerType' => MESSAGE_TYPE_EMAIL, 'identifier' => $email, ]))); $user->setAttribute('targets', [$target]); @@ -678,7 +678,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ], 'userId' => $userDoc->getId(), 'userInternalId' => $userDoc->getInternalId(), - 'providerType' => 'email', + 'providerType' => MESSAGE_TYPE_EMAIL, 'identifier' => $email, ])); } catch (Duplicate) { @@ -1732,7 +1732,7 @@ App::post('/v1/account/targets/push') ], 'providerId' => $providerId ?? null, 'providerInternalId' => $provider->getInternalId() ?? null, - 'providerType' => 'push', + 'providerType' => MESSAGE_TYPE_PUSH, 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), 'identifier' => $identifier, diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 0c1edb6a96..92af97f950 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -396,7 +396,7 @@ App::post('/v1/users/:userId/targets') ->label('sdk.response.model', Response::MODEL_TARGET) ->param('targetId', '', new CustomId(), 'Target 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('userId', '', new UID(), 'User ID.') - ->param('providerType', '', new WhiteList(['email', 'sms', 'push']), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.') + ->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.') ->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)') ->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true) ->inject('queueForEvents') @@ -407,7 +407,7 @@ App::post('/v1/users/:userId/targets') $provider = new Document(); - if ($providerType === 'push') { + if ($providerType === MESSAGE_TYPE_PUSH) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -422,13 +422,13 @@ App::post('/v1/users/:userId/targets') throw new Exception(Exception::GENERAL_INVALID_EMAIL); } break; - case 'sms': + case MESSAGE_TYPE_SMS: $validator = new Phone(); if (!$validator->isValid($identifier)) { throw new Exception(Exception::GENERAL_INVALID_PHONE); } break; - case 'push': + case MESSAGE_TYPE_PUSH: break; default: throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); @@ -1298,13 +1298,13 @@ App::patch('/v1/users/:userId/targets/:targetId') throw new Exception(Exception::GENERAL_INVALID_EMAIL); } break; - case 'sms': + case MESSAGE_TYPE_SMS: $validator = new Phone(); if (!$validator->isValid($identifier)) { throw new Exception(Exception::GENERAL_INVALID_PHONE); } break; - case 'push': + case MESSAGE_TYPE_PUSH: break; default: throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); diff --git a/app/init.php b/app/init.php index 6f61b955f3..800b32fdd0 100644 --- a/app/init.php +++ b/app/init.php @@ -190,6 +190,10 @@ const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB // Function headers const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host']; const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length']; +// Message types +const MESSAGE_TYPE_EMAIL = 'email'; +const MESSAGE_TYPE_SMS = 'sms'; +const MESSAGE_TYPE_PUSH = 'push'; // Usage metrics const METRIC_TEAMS = 'teams'; const METRIC_USERS = 'users'; @@ -607,11 +611,11 @@ Database::addFilter( $data = \json_decode($message->getAttribute('data', []), true); if (\array_key_exists('subject', $data)) { - $searchValues = \array_merge($searchValues, [$data['subject'], 'email']); + $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); } elseif (\array_key_exists('content', $data)) { - $searchValues = \array_merge($searchValues, [$data['content'], 'sms']); + $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); } else { - $searchValues = \array_merge($searchValues, [$data['title'], 'push']); + $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); } $search = \implode(' ', \array_filter($searchValues)); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 8488d8a9e4..200bb06212 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -67,7 +67,7 @@ class Messaging extends Action } if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) { - if ($payload['providerType'] === 'SMS') { + if ($payload['providerType'] === MESSAGE_TYPE_SMS) { $this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']); } } else { @@ -155,9 +155,9 @@ class Messaging extends Action $identifiers = $identifiersByProviderId[$providerId]; $adapter = match ($provider->getAttribute('type')) { - 'sms' => $this->sms($provider), - 'push' => $this->push($provider), - 'email' => $this->email($provider), + MESSAGE_TYPE_SMS => $this->sms($provider), + MESSAGE_TYPE_PUSH => $this->push($provider), + MESSAGE_TYPE_EMAIL => $this->email($provider), default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE) }; @@ -173,9 +173,9 @@ class Messaging extends Action $messageData->setAttribute('to', $batch); $data = match ($provider->getAttribute('type')) { - 'sms' => $this->buildSMSMessage($messageData, $provider), - 'push' => $this->buildPushMessage($messageData), - 'email' => $this->buildEmailMessage($messageData, $provider), + MESSAGE_TYPE_SMS => $this->buildSMSMessage($messageData, $provider), + MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData), + MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($messageData, $provider), default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE) }; @@ -245,7 +245,7 @@ class Messaging extends Action $provider = new Document([ '$id' => ID::unique(), 'provider' => $host, - 'type' => 'sms', + 'type' => MESSAGE_TYPE_SMS, 'name' => 'Internal SMS', 'enabled' => true, 'credentials' => match ($host) { diff --git a/src/Appwrite/Utopia/Response/Model/Provider.php b/src/Appwrite/Utopia/Response/Model/Provider.php index f8a0514020..d3de061aab 100644 --- a/src/Appwrite/Utopia/Response/Model/Provider.php +++ b/src/Appwrite/Utopia/Response/Model/Provider.php @@ -50,7 +50,7 @@ class Provider extends Model 'type' => self::TYPE_STRING, 'description' => 'Type of provider.', 'default' => '', - 'example' => 'sms', + 'example' => MESSAGE_TYPE_SMS, ]) ->addRule('credentials', [ 'type' => self::TYPE_JSON, From 82c86c0ae2e9a0d44a1c18cd6c4e8252023544b9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 29 Nov 2023 17:08:25 +1300 Subject: [PATCH 10/19] `deliveryTime` -> `scheduledAt` --- app/config/collections.php | 2 +- app/controllers/api/messaging.php | 68 +++++++++++++++---------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 7e715baa2c..d1db57c426 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1572,7 +1572,7 @@ $commonCollections = [ 'filters' => [], ], [ - '$id' => ID::custom('deliveryTime'), + '$id' => ID::custom('scheduledAt'), 'type' => Database::VAR_DATETIME, 'format' => '', 'size' => 0, diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index f13b037b02..85be4242c9 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -101,7 +101,7 @@ App::post('/v1/messaging/providers/mailgun') '$id' => $providerId, 'name' => $name, 'provider' => 'mailgun', - 'type' => 'email', + 'type' => MESSAGE_TYPE_EMAIL, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $options, @@ -172,7 +172,7 @@ App::post('/v1/messaging/providers/sendgrid') '$id' => $providerId, 'name' => $name, 'provider' => 'sendgrid', - 'type' => 'email', + 'type' => MESSAGE_TYPE_EMAIL, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $options, @@ -249,7 +249,7 @@ App::post('/v1/messaging/providers/msg91') '$id' => $providerId, 'name' => $name, 'provider' => 'msg91', - 'type' => 'sms', + 'type' => MESSAGE_TYPE_SMS, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $options, @@ -326,7 +326,7 @@ App::post('/v1/messaging/providers/telesign') '$id' => $providerId, 'name' => $name, 'provider' => 'telesign', - 'type' => 'sms', + 'type' => MESSAGE_TYPE_SMS, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $options, @@ -403,7 +403,7 @@ App::post('/v1/messaging/providers/textmagic') '$id' => $providerId, 'name' => $name, 'provider' => 'textmagic', - 'type' => 'sms', + 'type' => MESSAGE_TYPE_SMS, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $options, @@ -480,7 +480,7 @@ App::post('/v1/messaging/providers/twilio') '$id' => $providerId, 'name' => $name, 'provider' => 'twilio', - 'type' => 'sms', + 'type' => MESSAGE_TYPE_SMS, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $from, @@ -557,7 +557,7 @@ App::post('/v1/messaging/providers/vonage') '$id' => $providerId, 'name' => $name, 'provider' => 'vonage', - 'type' => 'sms', + 'type' => MESSAGE_TYPE_SMS, 'enabled' => $enabled, 'credentials' => $credentials, 'options' => $options, @@ -617,7 +617,7 @@ App::post('/v1/messaging/providers/fcm') '$id' => $providerId, 'name' => $name, 'provider' => 'fcm', - 'type' => 'push', + 'type' => MESSAGE_TYPE_PUSH, 'enabled' => $enabled, 'credentials' => $credentials ]); @@ -703,7 +703,7 @@ App::post('/v1/messaging/providers/apns') '$id' => $providerId, 'name' => $name, 'provider' => 'apns', - 'type' => 'push', + 'type' => MESSAGE_TYPE_PUSH, 'enabled' => $enabled, 'credentials' => $credentials, ]); @@ -2211,7 +2211,7 @@ App::post('/v1/messaging/messages/email') ->label('scope', 'messages.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createEmail') + ->label('sdk.method', 'createEmailMessage') ->label('sdk.description', '/docs/references/messaging/create-email.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -2225,13 +2225,13 @@ App::post('/v1/messaging/messages/email') ->param('description', '', new Text(256), 'Description for message.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), '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('dbForProject') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, string $description, string $status, bool $html, ?string $deliveryTime, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, string $description, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { @@ -2276,7 +2276,7 @@ App::post('/v1/messaging/messages/sms') ->label('scope', 'messages.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createSMS') + ->label('sdk.method', 'createSMSMessage') ->label('sdk.description', '/docs/references/messaging/create-sms.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -2288,13 +2288,13 @@ App::post('/v1/messaging/messages/sms') ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), '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('dbForProject') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $content, array $topics, array $users, array $targets, string $description, string $status, ?string $deliveryTime, 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; if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { @@ -2337,7 +2337,7 @@ App::post('/v1/messaging/messages/push') ->label('scope', 'messages.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'createPushNotification') + ->label('sdk.method', 'createPushMessage') ->label('sdk.description', '/docs/references/messaging/create-push-notification.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -2357,13 +2357,13 @@ App::post('/v1/messaging/messages/push') ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), '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('dbForProject') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, string $description, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $deliveryTime, 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; if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { @@ -2409,7 +2409,7 @@ App::post('/v1/messaging/messages/push') 'users' => $users, 'targets' => $targets, 'description' => $description, - 'deliveryTime' => $deliveryTime, + 'scheduledAt' => $scheduledAt, 'data' => $pushData, 'status' => $status, ])); @@ -2603,13 +2603,13 @@ App::patch('/v1/messaging/messages/email/:messageId') ->param('content', '', new Text(64230), 'Email Content.', true) ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), '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('dbForProject') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $subject, string $description, string $content, string $status, bool $html, ?string $deliveryTime, 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, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -2620,7 +2620,7 @@ App::patch('/v1/messaging/messages/email/:messageId') throw new Exception(Exception::MESSAGE_ALREADY_SENT); } - if (!is_null($message->getAttribute('deliveryTime')) && $message->getAttribute('deliveryTime') < new \DateTime()) { + if (!is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) { throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); } @@ -2660,8 +2660,8 @@ App::patch('/v1/messaging/messages/email/:messageId') $message->setAttribute('status', $status); } - if (!is_null($deliveryTime)) { - $message->setAttribute('deliveryTime', $deliveryTime); + if (!is_null($scheduledAt)) { + $message->setAttribute('scheduledAt', $scheduledAt); } $message = $dbForProject->updateDocument('messages', $message->getId(), $message); @@ -2701,13 +2701,13 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.', true) ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), '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('dbForProject') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $content, string $status, ?string $deliveryTime, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $content, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -2718,7 +2718,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') throw new Exception(Exception::MESSAGE_ALREADY_SENT); } - if (!is_null($message->getAttribute('deliveryTime')) && $message->getAttribute('deliveryTime') < new \DateTime()) { + if (!is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) { throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); } @@ -2750,8 +2750,8 @@ App::patch('/v1/messaging/messages/sms/:messageId') $message->setAttribute('description', $description); } - if (!is_null($deliveryTime)) { - $message->setAttribute('deliveryTime', $deliveryTime); + if (!is_null($scheduledAt)) { + $message->setAttribute('scheduledAt', $scheduledAt); } $message = $dbForProject->updateDocument('messages', $message->getId(), $message); @@ -2798,13 +2798,13 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) - ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), '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('dbForProject') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $title, string $body, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $deliveryTime, 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, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -2815,7 +2815,7 @@ App::patch('/v1/messaging/messages/push/:messageId') throw new Exception(Exception::MESSAGE_ALREADY_SENT); } - if (!is_null($message->getAttribute('deliveryTime')) && $message->getAttribute('deliveryTime') < new \DateTime()) { + if (!is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) { throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); } @@ -2879,8 +2879,8 @@ App::patch('/v1/messaging/messages/push/:messageId') $message->setAttribute('description', $description); } - if (!is_null($deliveryTime)) { - $message->setAttribute('deliveryTime', $deliveryTime); + if (!is_null($scheduledAt)) { + $message->setAttribute('scheduledAt', $scheduledAt); } $message = $dbForProject->updateDocument('messages', $message->getId(), $message); From 4b58d08fd8f0b6609c7d6292018cdd50bb494755 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 29 Nov 2023 17:09:44 +1300 Subject: [PATCH 11/19] Add `providerType` to `Message` --- app/config/collections.php | 11 +++++ app/controllers/api/messaging.php | 38 ++++------------ src/Appwrite/Event/Messaging.php | 14 +++--- .../Utopia/Response/Model/Message.php | 8 +++- tests/e2e/Services/GraphQL/Base.php | 44 ++++++++++--------- 5 files changed, 57 insertions(+), 58 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index d1db57c426..cbaed36f71 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1505,6 +1505,17 @@ $commonCollections = [ '$id' => ID::custom('messages'), 'name' => 'Messages', 'attributes' => [ + [ + '$id' => ID::custom('providerType'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('description'), 'type' => Database::VAR_STRING, diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 85be4242c9..411243d7b3 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -2240,6 +2240,7 @@ App::post('/v1/messaging/messages/email') $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, + 'providerType' => MESSAGE_TYPE_EMAIL, 'topics' => $topics, 'users' => $users, 'targets' => $targets, @@ -2303,6 +2304,7 @@ App::post('/v1/messaging/messages/sms') $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, + 'providerType' => MESSAGE_TYPE_SMS, 'topics' => $topics, 'users' => $users, 'targets' => $targets, @@ -2370,41 +2372,19 @@ App::post('/v1/messaging/messages/push') throw new Exception(Exception::MESSAGE_MISSING_TARGET); } - $pushData = [ - 'title' => $title, - 'body' => $body, - ]; + $pushData = []; - if (!is_null($data)) { - $pushData['data'] = $data; - } + $keys = ['title', 'body', 'data', 'action', 'icon', 'sound', 'color', 'tag', 'badge']; - if ($action) { - $pushData['action'] = $action; - } - - if ($icon) { - $pushData['icon'] = $icon; - } - - if ($sound) { - $pushData['sound'] = $sound; - } - - if ($color) { - $pushData['color'] = $color; - } - - if ($tag) { - $pushData['tag'] = $tag; - } - - if ($badge) { - $pushData['badge'] = $badge; + foreach ($keys as $key) { + if (!empty($$key)) { + $pushData[$key] = $$key; + } } $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, + 'providerType' => MESSAGE_TYPE_PUSH, 'topics' => $topics, 'users' => $users, 'targets' => $targets, diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index 62d41f8c3b..9201799355 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -11,7 +11,7 @@ class Messaging extends Event protected ?string $messageId = null; protected ?Document $message = null; protected ?array $recipients = null; - protected ?string $deliveryTime = null; + protected ?string $scheduledAt = null; protected ?string $providerType = null; @@ -117,14 +117,14 @@ class Messaging extends Event } /** - * Sets Delivery time for the messaging event. + * Sets Scheduled delivery time for the messaging event. * - * @param string $deliveryTime + * @param string $scheduledAt * @return self */ - public function setDeliveryTime(string $deliveryTime): self + public function setScheduledAt(string $scheduledAt): self { - $this->deliveryTime = $deliveryTime; + $this->scheduledAt = $scheduledAt; return $this; } @@ -134,9 +134,9 @@ class Messaging extends Event * * @return string */ - public function getDeliveryTime(): string + public function getScheduledAt(): string { - return $this->deliveryTime; + return $this->scheduledAt; } /** diff --git a/src/Appwrite/Utopia/Response/Model/Message.php b/src/Appwrite/Utopia/Response/Model/Message.php index 27c70d7073..32c67b6936 100644 --- a/src/Appwrite/Utopia/Response/Model/Message.php +++ b/src/Appwrite/Utopia/Response/Model/Message.php @@ -29,6 +29,12 @@ class Message extends Any 'default' => '', 'example' => self::TYPE_DATETIME_EXAMPLE, ]) + ->addRule('providerType', [ + 'type' => self::TYPE_STRING, + 'description' => 'Message provider type.', + 'default' => '', + 'example' => MESSAGE_TYPE_EMAIL, + ]) ->addRule('topics', [ 'type' => self::TYPE_STRING, 'description' => 'Topic IDs set as recipients.', @@ -50,7 +56,7 @@ class Message extends Any 'array' => true, 'example' => ['5e5ea5c16897e'], ]) - ->addRule('deliveryTime', [ + ->addRule('scheduledAt', [ 'type' => self::TYPE_DATETIME, 'description' => 'The scheduled time for message.', 'required' => false, diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 6939e6a9b5..2854d0bf42 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -2098,13 +2098,13 @@ trait Base } }'; case self::$CREATE_EMAIL: - return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $deliveryTime: String) { - messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) { + return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $scheduledAt: String) { + messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) { _id topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2113,13 +2113,13 @@ trait Base } }'; case self::$CREATE_SMS: - return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $deliveryTime: String) { - messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) { + return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $scheduledAt: String) { + messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) { _id topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2128,13 +2128,13 @@ trait Base } }'; case self::$CREATE_PUSH_NOTIFICATION: - return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $deliveryTime: String) { - messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, deliveryTime: $deliveryTime) { + return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) { + messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) { _id topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2148,10 +2148,11 @@ trait Base total messages { _id + providerType topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2164,10 +2165,11 @@ trait Base return 'query getMessage($messageId: String!) { messagingGetMessage(messageId: $messageId) { _id + providerType topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2176,13 +2178,13 @@ trait Base } }'; case self::$UPDATE_EMAIL: - return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $deliveryTime: String) { - messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) { + return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $scheduledAt: String) { + messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) { _id topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2191,13 +2193,13 @@ trait Base } }'; case self::$UPDATE_SMS: - return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $deliveryTime: String) { - messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) { + return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $scheduledAt: String) { + messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) { _id topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2206,13 +2208,13 @@ trait Base } }'; case self::$UPDATE_PUSH_NOTIFICATION: - return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $deliveryTime: String) { - messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, deliveryTime: $deliveryTime) { + return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) { + messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) { _id topics users targets - deliveryTime + scheduledAt deliveredAt deliveryErrors deliveredTotal @@ -2472,7 +2474,7 @@ trait Base protected string $stdout = ''; protected string $stderr = ''; - protected function packageCode($folder) + protected function packageCode($folder): void { Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); } From 042c1a725f7a7add6e77afcc381729e4c46a92c9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 29 Nov 2023 17:10:53 +1300 Subject: [PATCH 12/19] `Message` extends `Model` instead of `Any` so rules are applied since `data` is not hoisted --- src/Appwrite/Utopia/Response/Model/Message.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Message.php b/src/Appwrite/Utopia/Response/Model/Message.php index 32c67b6936..bd9c8c6c55 100644 --- a/src/Appwrite/Utopia/Response/Model/Message.php +++ b/src/Appwrite/Utopia/Response/Model/Message.php @@ -5,8 +5,9 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; use Utopia\Database\DateTime; +use Utopia\Database\Document as DatabaseDocument; -class Message extends Any +class Message extends Model { public function __construct() { From 43b8fc2c31ebe4645ec954a8d574637708988e87 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Wed, 29 Nov 2023 13:47:44 +0530 Subject: [PATCH 13/19] review changes --- app/controllers/api/users.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 0b099cb8d3..1cbb3e9632 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1161,7 +1161,7 @@ App::patch('/v1/users/:userId/email') Query::equal('identifier', [$email]), ]); - if ($target && !$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } @@ -1232,7 +1232,7 @@ App::patch('/v1/users/:userId/phone') Query::equal('identifier', [$number]), ]); - if ($target && !$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } From 739cc36fdc88a032f6f6d66d090fd85dae79fca7 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Wed, 29 Nov 2023 15:22:26 +0530 Subject: [PATCH 14/19] updated providerType wherever left to update --- app/controllers/api/account.php | 6 +-- app/init.php | 5 ++- composer.lock | 75 +++++++++++++++++---------------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c80d2d4387..68e3261a8d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1315,7 +1315,7 @@ App::post('/v1/account/sessions/phone') $target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([ 'userId' => $user->getId(), 'userInternalId' => $user->getInternalId(), - 'providerType' => 'sms', + 'providerType' => MESSAGE_TYPE_SMS, 'identifier' => $phone, ]))); $user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]); @@ -1374,7 +1374,7 @@ App::post('/v1/account/sessions/phone') $queueForMessaging ->setMessage($messageDoc) ->setRecipients([$phone]) - ->setProviderType('SMS') + ->setProviderType(MESSAGE_TYPE_SMS) ->setProject($project) ->trigger(); @@ -3100,7 +3100,7 @@ App::post('/v1/account/verification/phone') $queueForMessaging ->setMessage($messageDoc) ->setRecipients([$user->getAttribute('phone')]) - ->setProviderType('SMS') + ->setProviderType(MESSAGE_TYPE_SMS) ->setProject($project) ->trigger(); diff --git a/app/init.php b/app/init.php index 800b32fdd0..4dfdcaf108 100644 --- a/app/init.php +++ b/app/init.php @@ -609,10 +609,11 @@ Database::addFilter( ]; $data = \json_decode($message->getAttribute('data', []), true); + $providerType = $message->getAttribute('providerType', ''); - if (\array_key_exists('subject', $data)) { + if ($providerType === MESSAGE_TYPE_EMAIL) { $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); - } elseif (\array_key_exists('content', $data)) { + } elseif ($providerType === MESSAGE_TYPE_SMS) { $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); } else { $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); diff --git a/composer.lock b/composer.lock index afa71aaf2b..16f44a6357 100644 --- a/composer.lock +++ b/composer.lock @@ -156,11 +156,11 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.13.1", + "version": "0.13.2", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610" + "reference": "214a37c2c66e0f2bc9c30fdfde66955d9fd084a1" }, "require": { "php": ">=8.0", @@ -195,7 +195,7 @@ "php", "runtimes" ], - "time": "2023-10-16T15:39:53+00:00" + "time": "2023-11-22T15:36:00+00:00" }, { "name": "chillerlan/php-qrcode", @@ -1465,7 +1465,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -1512,7 +1512,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2217,16 +2217,16 @@ }, { "name": "utopia-php/logger", - "version": "0.3.1", + "version": "0.3.2", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc" + "reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/de623f1ec1c672c795d113dd25c5bf212f7ef4fc", - "reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/ba763c10688fe2ed715ad2bed3f13d18dfec6253", + "reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253", "shasum": "" }, "require": { @@ -2264,9 +2264,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.3.1" + "source": "https://github.com/utopia-php/logger/tree/0.3.2" }, - "time": "2023-02-10T15:52:50+00:00" + "time": "2023-11-22T14:45:43+00:00" }, { "name": "utopia-php/messaging", @@ -3136,16 +3136,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.35.2", + "version": "0.35.3", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b" + "reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/2dfe0430a64ffd2a07078d83b20144b871acac3b", - "reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/4c431d5324a8f8cd2cab9a5515c170a5b427d44c", + "reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c", "shasum": "" }, "require": { @@ -3181,9 +3181,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.35.2" + "source": "https://github.com/appwrite/sdk-generator/tree/0.35.3" }, - "time": "2023-09-14T14:59:50+00:00" + "time": "2023-11-12T05:56:27+00:00" }, { "name": "doctrine/deprecations", @@ -3890,16 +3890,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.2", + "version": "1.24.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bcad8d995980440892759db0c32acae7c8e79442" + "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", - "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", "shasum": "" }, "require": { @@ -3931,9 +3931,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.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" }, - "time": "2023-09-26T12:28:12+00:00" + "time": "2023-11-26T18:29:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5676,16 +5676,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -5714,7 +5714,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -5722,30 +5722,31 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { @@ -5781,7 +5782,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -5793,7 +5794,7 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-11-21T18:54:41+00:00" } ], "aliases": [], From 6ead6f80943797a0910eeb6dbf74aecd07aa8f17 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Fri, 1 Dec 2023 03:39:43 +0530 Subject: [PATCH 15/19] misc changes allow filtering by providerType, userId in subscribers. Adds cancelled status for message status. Validate targets when creating message. delete all targets when user is deleted. fix twilio bug. add db env vars in messaging worker compose.phtml --- app/config/collections.php | 47 +++++++++ app/config/errors.php | 15 +++ app/controllers/api/messaging.php | 95 +++++++++++++++++-- app/controllers/api/users.php | 4 + app/views/install/compose.phtml | 10 +- src/Appwrite/Extend/Exception.php | 4 + src/Appwrite/Platform/Workers/Deletes.php | 5 + src/Appwrite/Platform/Workers/Messaging.php | 6 +- .../Validator/Queries/Subscribers.php | 4 +- .../Utopia/Response/Model/Message.php | 2 +- .../Utopia/Response/Model/Subscriber.php | 12 +++ src/Appwrite/Utopia/Response/Model/Target.php | 2 +- 12 files changed, 192 insertions(+), 14 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index cbaed36f71..77e2be68ba 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1755,6 +1755,28 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('userId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('userInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('topicId'), 'type' => Database::VAR_STRING, @@ -1777,6 +1799,17 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('providerType'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ @@ -1793,6 +1826,20 @@ $commonCollections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_userId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['_key_userId'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_userInternalId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['userInternalId'], + 'lengths' => [], + 'orders' => [], + ], [ '$id' => ID::custom('_key_topicId'), 'type' => Database::INDEX_KEY, diff --git a/app/config/errors.php b/app/config/errors.php index 3c0b67c76a..64143dc538 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -847,5 +847,20 @@ return [ 'description' => 'Message with the requested ID has already been scheduled for delivery.', 'code' => 400, ], + Exception::MESSAGE_TARGET_NOT_EMAIL => [ + 'name' => Exception::MESSAGE_TARGET_NOT_EMAIL, + 'description' => 'Message with the target ID is not an Email target:', + 'code' => 400, + ], + Exception::MESSAGE_TARGET_NOT_SMS => [ + 'name' => Exception::MESSAGE_TARGET_NOT_SMS, + 'description' => 'Message with the target ID is not an SMS target:', + 'code' => 400, + ], + Exception::MESSAGE_TARGET_NOT_PUSH => [ + 'name' => Exception::MESSAGE_TARGET_NOT_PUSH, + 'description' => 'Message with the target ID is not a Push target:', + 'code' => 400, + ], ]; diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 411243d7b3..682da9995e 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -483,7 +483,7 @@ App::post('/v1/messaging/providers/twilio') 'type' => MESSAGE_TYPE_SMS, 'enabled' => $enabled, 'credentials' => $credentials, - 'options' => $from, + 'options' => $options, ]); try { @@ -1952,6 +1952,9 @@ App::post('/v1/messaging/topics/:topicId/subscribers') 'topicInternalId' => $topic->getInternalId(), 'targetId' => $targetId, 'targetInternalId' => $target->getInternalId(), + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'providerType' => $target->getAttribute('providerType'), ]); try { @@ -1987,11 +1990,16 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->label('sdk.response.model', Response::MODEL_SUBSCRIBER_LIST) ->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.') ->param('queries', [], new Subscribers(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Providers::ALLOWED_ATTRIBUTES), true) + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') ->action(function (string $topicId, array $queries, Database $dbForProject, Response $response) { $queries = Query::parseQueries($queries); + if (!empty($search)) { + $queries[] = Query::search('search', $search); + } + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { @@ -2223,7 +2231,7 @@ App::post('/v1/messaging/messages/email') ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for message.', true) - ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) + ->param('status', 'processing', new WhiteList(['draft', '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('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') @@ -2238,6 +2246,18 @@ App::post('/v1/messaging/messages/email') throw new Exception(Exception::MESSAGE_MISSING_TARGET); } + foreach ($targets as $target) { + $targetDocument = $dbForProject->getDocument('targets', $target); + + if ($targetDocument->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_EMAIL) { + throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId()); + } + } + $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, 'providerType' => MESSAGE_TYPE_EMAIL, @@ -2288,7 +2308,7 @@ App::post('/v1/messaging/messages/sms') ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) - ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) + ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') @@ -2302,6 +2322,18 @@ App::post('/v1/messaging/messages/sms') throw new Exception(Exception::MESSAGE_MISSING_TARGET); } + foreach ($targets as $target) { + $targetDocument = $dbForProject->getDocument('targets', $target); + + if ($targetDocument->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_SMS) { + throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS . ' ' . $targetDocument->getId()); + } + } + $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, 'providerType' => MESSAGE_TYPE_SMS, @@ -2358,7 +2390,7 @@ App::post('/v1/messaging/messages/push') ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) - ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) + ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') @@ -2372,6 +2404,18 @@ App::post('/v1/messaging/messages/push') throw new Exception(Exception::MESSAGE_MISSING_TARGET); } + foreach ($targets as $target) { + $targetDocument = $dbForProject->getDocument('targets', $target); + + if ($targetDocument->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) { + throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH . ' ' . $targetDocument->getId()); + } + } + $pushData = []; $keys = ['title', 'body', 'data', 'action', 'icon', 'sound', 'color', 'tag', 'badge']; @@ -2581,7 +2625,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->param('subject', '', new Text(998), 'Email Subject.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.', true) - ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) + ->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) ->param('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') @@ -2613,6 +2657,18 @@ App::patch('/v1/messaging/messages/email/:messageId') } if (!\is_null($targets)) { + foreach ($targets as $target) { + $targetDocument = $dbForProject->getDocument('targets', $target); + + if ($targetDocument->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_EMAIL) { + throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId()); + } + } + $message->setAttribute('targets', $targets); } @@ -2680,7 +2736,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.', true) - ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) + ->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') @@ -2711,6 +2767,18 @@ App::patch('/v1/messaging/messages/sms/:messageId') } if (!\is_null($targets)) { + foreach ($targets as $target) { + $targetDocument = $dbForProject->getDocument('targets', $target); + + if ($targetDocument->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_SMS) { + throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS . ' ' . $targetDocument->getId()); + } + } + $message->setAttribute('targets', $targets); } @@ -2777,7 +2845,8 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true) ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) - ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) + ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) + ->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') @@ -2808,6 +2877,18 @@ App::patch('/v1/messaging/messages/push/:messageId') } if (!\is_null($targets)) { + foreach ($targets as $target) { + $targetDocument = $dbForProject->getDocument('targets', $target); + + if ($targetDocument->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) { + throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH . ' ' . $targetDocument->getId()); + } + } + $message->setAttribute('targets', $targets); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4312ac13da..8a71f33d8b 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1397,6 +1397,10 @@ App::patch('/v1/users/:userId/targets/:targetId') throw new Exception(Exception::PROVIDER_NOT_FOUND); } + if ($provider->getAttribute('type') !== $target->getAttribute('providerType')) { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); + } + $target->setAttribute('providerId', $provider->getId()); $target->setAttribute('providerInternalId', $provider->getInternalId()); } diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 898b46b3a5..252b5b6bd7 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -521,14 +521,20 @@ services: environment: - _APP_ENV - _APP_WORKER_PER_CORE + - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT - _APP_REDIS_USER - _APP_REDIS_PASS - - _APP_SMS_PROVIDER - - _APP_SMS_FROM + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_SMS_FROM + - _APP_SMS_PROVIDER appwrite-worker-migrations: image: /: diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 61ea597941..5c928569aa 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -256,6 +256,10 @@ class Exception extends \Exception public const MESSAGE_MISSING_TARGET = 'message_missing_target'; public const MESSAGE_ALREADY_SENT = 'message_already_sent'; public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled'; + public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email'; + public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms'; + public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push'; + protected string $type = ''; protected array $errors = []; diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 74365bad86..9a9e965f37 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -556,6 +556,11 @@ class Deletes extends Action $this->deleteByGroup('identities', [ Query::equal('userInternalId', [$userInternalId]) ], $dbForProject); + + // Delete targets + $this->deleteByGroup('targets', [ + Query::equal('userInternalId', [$userInternalId]) + ], $dbForProject); } /** diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 36647e9b7a..8f945e947f 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -91,14 +91,16 @@ class Messaging extends Action if (\count($topicsId) > 0) { $topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]); foreach ($topics as $topic) { - $recipients = \array_merge($recipients, $topic->getAttribute('targets')); + $targets = \array_filter($topic->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType')); + $recipients = \array_merge($recipients, $targets); } } if (\count($usersId) > 0) { $users = $dbForProject->find('users', [Query::equal('$id', $usersId)]); foreach ($users as $user) { - $recipients = \array_merge($recipients, $user->getAttribute('targets')); + $targets = \array_filter($user->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType')); + $recipients = \array_merge($recipients, $targets); } } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php index 55bb455903..05e08a75a7 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php @@ -6,7 +6,9 @@ class Subscribers extends Base { public const ALLOWED_ATTRIBUTES = [ 'targetId', - 'topicId' + 'topicId', + 'userId', + 'providerType' ]; /** diff --git a/src/Appwrite/Utopia/Response/Model/Message.php b/src/Appwrite/Utopia/Response/Model/Message.php index bd9c8c6c55..791c87933f 100644 --- a/src/Appwrite/Utopia/Response/Model/Message.php +++ b/src/Appwrite/Utopia/Response/Model/Message.php @@ -98,7 +98,7 @@ class Message extends Model 'type' => self::TYPE_STRING, 'description' => 'Status of delivery.', 'default' => 'processing', - 'example' => 'Message status can be one of the following: processing, sent, failed.', + 'example' => 'Message status can be one of the following: processing, sent, cancelled, failed.', ]) ->addRule('description', [ 'type' => self::TYPE_STRING, diff --git a/src/Appwrite/Utopia/Response/Model/Subscriber.php b/src/Appwrite/Utopia/Response/Model/Subscriber.php index e699df876c..8c3a4c7a49 100644 --- a/src/Appwrite/Utopia/Response/Model/Subscriber.php +++ b/src/Appwrite/Utopia/Response/Model/Subscriber.php @@ -49,6 +49,12 @@ class Subscriber extends Model 'userId' => '5e5ea5c16897e', ], ]) + ->addRule('userId', [ + 'type' => self::TYPE_STRING, + 'description' => 'Topic ID.', + 'default' => '', + 'example' => '5e5ea5c16897e', + ]) ->addRule('userName', [ 'type' => self::TYPE_STRING, 'description' => 'User Name.', @@ -60,6 +66,12 @@ class Subscriber extends Model 'description' => 'Topic ID.', 'default' => '', 'example' => '259125845563242502', + ]) + ->addRule('providerType', [ + 'type' => self::TYPE_STRING, + 'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.', + 'default' => '', + 'example' => MESSAGE_TYPE_EMAIL, ]); } diff --git a/src/Appwrite/Utopia/Response/Model/Target.php b/src/Appwrite/Utopia/Response/Model/Target.php index a92d1b34ca..d180b6c4c4 100644 --- a/src/Appwrite/Utopia/Response/Model/Target.php +++ b/src/Appwrite/Utopia/Response/Model/Target.php @@ -51,7 +51,7 @@ class Target extends Model 'type' => self::TYPE_STRING, 'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.', 'default' => '', - 'example' => 'email', + 'example' => MESSAGE_TYPE_EMAIL, ]) ->addRule('identifier', [ 'type' => self::TYPE_STRING, From 81ec4ff9bc1b2f3029d4d753b8abca5439eb5147 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Fri, 1 Dec 2023 03:51:03 +0530 Subject: [PATCH 16/19] fix typo in userId index in targets --- app/config/collections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/collections.php b/app/config/collections.php index 77e2be68ba..b3e3417555 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1829,7 +1829,7 @@ $commonCollections = [ [ '$id' => ID::custom('_key_userId'), 'type' => Database::INDEX_KEY, - 'attributes' => ['_key_userId'], + 'attributes' => ['userId'], 'lengths' => [], 'orders' => [], ], From 5ed8bdae35f9e59acee18b58b30aee9132c12cbf Mon Sep 17 00:00:00 2001 From: prateek banga Date: Fri, 1 Dec 2023 03:55:29 +0530 Subject: [PATCH 17/19] fix search param in list subscribers endpoint --- app/controllers/api/messaging.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 682da9995e..867f55ec0c 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1993,7 +1993,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, array $queries, Database $dbForProject, Response $response) { + ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) { $queries = Query::parseQueries($queries); if (!empty($search)) { From f9dd40120ecd19e4370a9a83100c16cdb5d41f26 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Mon, 4 Dec 2023 15:48:37 +0100 Subject: [PATCH 18/19] resolve update twilio provider --- app/controllers/api/messaging.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 867f55ec0c..33c85857d0 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1289,8 +1289,8 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('accountSid', null, new Text(0), 'Twilio account secret ID.', true) - ->param('authToken', null, new Text(0), 'Twilio authentication token.', true) + ->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true) + ->param('authToken', '', new Text(0), 'Twilio authentication token.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') From 54f574906cafe6b7ac7cc316be74b67b70a45c03 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Tue, 5 Dec 2023 12:22:32 +0100 Subject: [PATCH 19/19] review changes --- app/config/errors.php | 4 ++-- app/controllers/api/messaging.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 64143dc538..4c811dd53e 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -849,7 +849,7 @@ return [ ], Exception::MESSAGE_TARGET_NOT_EMAIL => [ 'name' => Exception::MESSAGE_TARGET_NOT_EMAIL, - 'description' => 'Message with the target ID is not an Email target:', + 'description' => 'Message with the target ID is not an email target:', 'code' => 400, ], Exception::MESSAGE_TARGET_NOT_SMS => [ @@ -859,7 +859,7 @@ return [ ], Exception::MESSAGE_TARGET_NOT_PUSH => [ 'name' => Exception::MESSAGE_TARGET_NOT_PUSH, - 'description' => 'Message with the target ID is not a Push target:', + 'description' => 'Message with the target ID is not a push target:', 'code' => 400, ], diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 33c85857d0..f0cb4dd966 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -2231,7 +2231,7 @@ App::post('/v1/messaging/messages/email') ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for message.', true) - ->param('status', 'processing', new WhiteList(['draft', '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('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') @@ -2308,7 +2308,7 @@ App::post('/v1/messaging/messages/sms') ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) - ->param('status', 'processing', new WhiteList(['draft', '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) ->inject('queueForEvents') ->inject('dbForProject') @@ -2390,7 +2390,7 @@ App::post('/v1/messaging/messages/push') ->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true) ->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true) ->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) - ->param('status', 'processing', new WhiteList(['draft', '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) ->inject('queueForEvents') ->inject('dbForProject')