From 52f6b72f2377ccd15dc4bb86bdcf63c1061afc4b Mon Sep 17 00:00:00 2001 From: prateek banga Date: Fri, 25 Aug 2023 01:45:17 +0530 Subject: [PATCH 01/18] adds topic controllers --- app/config/errors.php | 9 +- app/controllers/api/messaging.php | 210 +++++++++++++++++- app/init.php | 21 +- src/Appwrite/Extend/Exception.php | 3 + .../Database/Validator/Queries/Topics.php | 20 ++ 5 files changed, 242 insertions(+), 21 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Topics.php diff --git a/app/config/errors.php b/app/config/errors.php index b4b2429f31..97b5f0661c 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -707,5 +707,12 @@ return [ 'name' => Exception::PROVIDER_INCORRECT_TYPE, 'description' => 'Provider with the request ID is of incorrect type: ', 'code' => 400, - ] + ], + + /** Topic Errors */ + Exception::TOPIC_NOT_FOUND => [ + 'name' => Exception::TOPIC_NOT_FOUND, + 'description' => 'Provider with the request ID could not be found.', + 'code' => 404, + ], ]; diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 753abc8432..df7744f798 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -2,11 +2,15 @@ use Appwrite\Event\Event; use Appwrite\Extend\Exception; +use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Providers; +use Appwrite\Utopia\Database\Validator\Queries\Topics; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Helpers\Permission; +use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime; @@ -25,7 +29,7 @@ App::get('/v1/messaging/providers') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER_LIST) - ->param('queries', [], new Providers(), '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('queries', [], new Providers(), '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) ->inject('dbForProject') ->inject('response') ->action(function (array $queries, Database $dbForProject, Response $response) { @@ -144,7 +148,7 @@ App::patch('/v1/messaging/providers/:id/mailgun') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'mailgun') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -229,7 +233,7 @@ App::patch('/v1/messaging/providers/:id/sendgrid') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'sendgrid') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -313,7 +317,7 @@ App::patch('/v1/messaging/providers/:id/msg91') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'msg91') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -401,7 +405,7 @@ App::patch('/v1/messaging/providers/:id/telesign') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'telesign') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -489,7 +493,7 @@ App::patch('/v1/messaging/providers/:id/textmagic') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'text-magic') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -577,7 +581,7 @@ App::patch('/v1/messaging/providers/:id/twilio') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'twilio') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -665,7 +669,7 @@ App::patch('/v1/messaging/providers/:id/vonage') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'vonage') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -753,7 +757,7 @@ App::patch('/v1/messaging/providers/:id/fcm') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'fcm') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -841,7 +845,7 @@ App::patch('/v1/messaging/providers/:id/apns') $providerAttr = $provider->getAttribute('provider'); if ($providerAttr !== 'apns') { - throw new Exception(Exception::PROVIDER_INCORRECT_TYPE.$providerAttr); + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); } if ($name) { @@ -900,6 +904,192 @@ App::delete('/v1/messaging/providers/:id') $response->noContent(); }); +App::get('/v1/messaging/topics') + ->desc('List topics.') + ->groups(['api', 'messaging']) + ->label('scope', 'topics.read') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'listTopics') + ->label('sdk.description', '/docs/references/messaging/list-topics.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TOPIC_LIST) + ->param('queries', [], new Topics(), '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(', ', Topics::ALLOWED_ATTRIBUTES), true) + ->inject('dbForProject') + ->inject('response') + ->action(function (array $queries, Database $dbForProject, Response $response) { + $queries = Query::parseQueries($queries); + + // Get cursor document if there was a cursor query + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = reset($cursor); + + if ($cursor) { + $topicId = $cursor->getValue(); + $cursorDocument = Authorization::skip(fn () => $dbForProject->find('topics', [ + Query::equal('$id', [$topicId]), + Query::limit(1), + ])); + + if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) { + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found."); + } + + $cursor->setValue($cursorDocument[0]); + } + + $filterQueries = Query::groupByType($queries)['filters']; + $response->dynamic(new Document([ + 'total' => $dbForProject->count('topics', $filterQueries, APP_LIMIT_COUNT), + 'indexes' => $dbForProject->find('topics', $queries), + ]), Response::MODEL_TOPIC_LIST); + }); + +App::get('/v1/messaging/topics/:id') + ->desc('Get a topic.') + ->groups(['api', 'messaging']) + ->label('scope', 'topics.read') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'getTopic') + ->label('sdk.description', '/docs/references/messaging/get-topic.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TOPIC) + ->param('id', '', new UID(), 'Topic ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, Database $dbForProject, Response $response) { + $topic = $dbForProject->getDocument('topics', $id); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + $topic = $dbForProject->getDocument('topics', $id); + + $response + ->dynamic($topic, Response::MODEL_TOPIC); + }); + +App::post('/v1/messaging/topics') + ->desc('Create a topic.') + ->groups(['api', 'messaging']) + ->label('audits.event', 'topics.create') + ->label('audits.resource', 'topics/{response.$id}') + ->label('scope', 'topics.write') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createTopic') + ->label('sdk.description', '/docs/references/messaging/create-topic.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TOPIC) + ->param('id', '', new CustomId(), 'Topic ID. Choose a custom Topic ID or a new Topic ID.') + ->param('providerId', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Topic Name.') + ->param('description', '', new Text(2048), 'Topic Description.', true) + ->inject('user') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $providerId, string $name, string $description, Document $user, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $providerId); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $topic = new Document([ + '$id' => $id, + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], + 'providerId' => $providerId, + 'providerInternalId' => $provider->getInternalId(), + 'name' => $name, + + ]); + + if ($description) { + $topic->setAttribute('description', $description); + } + + $topic = $dbForProject->createDocument('topics', $topic); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($topic, Response::MODEL_TOPIC); + }); + +App::patch('/v1/messaging/topics/:id') + ->desc('Update a topic.') + ->groups(['api', 'messaging']) + ->label('audits.event', 'topics.update') + ->label('audits.resource', 'topics/{response.$id}') + ->label('scope', 'topics.write') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateTopic') + ->label('sdk.description', '/docs/references/messaging/update-topic.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TOPIC) + ->param('id', '', new UID(), 'Topic ID.') + ->param('name', '', new Text(128), 'Topic Name.', true) + ->param('description', null, new Text(128), 'Topic Description.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $description, Database $dbForProject, Response $response) { + $topic = $dbForProject->getDocument('topics', $id); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + if ($name) { + $topic->setAttribute('name', $name); + } + + if ($description) { + $topic->setAttribute('description', $description); + } + + $topic = $dbForProject->updateDocument('topics', $id, $topic); + + $response + ->dynamic($topic, Response::MODEL_TOPIC); + }); + +App::delete('/v1/messaging/topics/:id') + ->desc('Delete a topic.') + ->groups(['api', 'messaging']) + ->label('audits.event', 'topics.delete') + ->label('audits.resource', 'topics/{request.id}') + ->label('scope', 'topics.write') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'deleteTopic') + ->label('sdk.description', '/docs/references/messaging/delete-topic.md') + ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('id', '', new UID(), 'Topic ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, Database $dbForProject, Response $response) { + $topic = $dbForProject->getDocument('topics', $id); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + $topic = $dbForProject->deleteDocument('topics', $id); + $response->noContent(); + }); + App::post('/v1/messaging/messages/email') ->desc('Send an email.') ->groups(['api', 'messaging']) diff --git a/app/init.php b/app/init.php index a71df23bf0..fa026df4cf 100644 --- a/app/init.php +++ b/app/init.php @@ -544,11 +544,10 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { $provider = Authorization::skip(fn () => $database ->getDocument( - 'providers', - $document->getAttribute('providerId'), + 'providers', + $document->getAttribute('providerId'), [Query::select(['type'])] - ) - ); + )); if ($provider) { return $provider->getAttribute('type'); } @@ -563,12 +562,13 @@ Database::addFilter( }, function (mixed $value, Document $document, Database $database) { $topicIds = Authorization::skip(fn () => \array_map( - fn ($document) => $document->getAttribute('topicId'), $database + fn ($document) => $document->getAttribute('topicId'), + $database ->find('subscribers', [ Query::equal('targetInternalId', [$document->getInternalId()]), Query::limit(APP_LIMIT_SUBQUERY), - ])) - ); + ]) + )); return $database->find('topics', [Query::equal('$id', $topicIds)]); } ); @@ -580,12 +580,13 @@ Database::addFilter( }, function (mixed $value, Document $document, Database $database) { $targetIds = Authorization::skip(fn () => \array_map( - fn ($document) => $document->getAttribute('targetId'), $database + fn ($document) => $document->getAttribute('targetId'), + $database ->find('subscribers', [ Query::equal('topicInternalId', [$document->getInternalId()]), Query::limit(APP_LIMIT_SUBQUERY), - ])) - ); + ]) + )); return $database->find('targets', [Query::equal('$id', $targetIds)]); } ); diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 5ab806b254..4b5cac24a8 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -217,6 +217,9 @@ class Exception extends \Exception public const PROVIDER_NOT_FOUND = 'provider_not_found'; public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; + /** Topic */ + public const TOPIC_NOT_FOUND = 'topic_not_found'; + protected $type = ''; protected $errors = []; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Topics.php b/src/Appwrite/Utopia/Database/Validator/Queries/Topics.php new file mode 100644 index 0000000000..120b3edc3f --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Topics.php @@ -0,0 +1,20 @@ + Date: Fri, 25 Aug 2023 04:36:41 +0530 Subject: [PATCH 02/18] adds test cases for topic controller --- app/config/roles.php | 4 +- app/controllers/api/messaging.php | 48 +++++----- tests/e2e/Scopes/ProjectCustom.php | 2 + .../e2e/Services/Messaging/MessagingBase.php | 90 ++++++++++++++++++- .../Messaging/MessagingConsoleClientTest.php | 15 ++++ .../Messaging/MessagingCustomClientTest.php | 15 ++++ 6 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 tests/e2e/Services/Messaging/MessagingConsoleClientTest.php create mode 100644 tests/e2e/Services/Messaging/MessagingCustomClientTest.php diff --git a/app/config/roles.php b/app/config/roles.php index 4bc85f7a28..4666268d0c 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -25,7 +25,9 @@ $member = [ 'providers.write', 'providers.read', 'messages.write', - 'messages.read' + 'messages.read', + 'topics.write', + 'topics.read' ]; $admins = [ diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index a0bf2d2ead..4f14903f0c 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -936,7 +936,7 @@ App::get('/v1/messaging/topics') ->desc('List topics.') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopics') ->label('sdk.description', '/docs/references/messaging/list-topics.md') @@ -970,7 +970,7 @@ App::get('/v1/messaging/topics') $filterQueries = Query::groupByType($queries)['filters']; $response->dynamic(new Document([ 'total' => $dbForProject->count('topics', $filterQueries, APP_LIMIT_COUNT), - 'indexes' => $dbForProject->find('topics', $queries), + 'topics' => $dbForProject->find('topics', $queries), ]), Response::MODEL_TOPIC_LIST); }); @@ -978,7 +978,7 @@ App::get('/v1/messaging/topics/:id') ->desc('Get a topic.') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getTopic') ->label('sdk.description', '/docs/references/messaging/get-topic.md') @@ -1007,21 +1007,21 @@ App::post('/v1/messaging/topics') ->label('audits.event', 'topics.create') ->label('audits.resource', 'topics/{response.$id}') ->label('scope', 'topics.write') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTopic') ->label('sdk.description', '/docs/references/messaging/create-topic.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOPIC) - ->param('id', '', new CustomId(), 'Topic ID. Choose a custom Topic ID or a new Topic ID.') + ->param('topicId', '', new CustomId(), 'Topic ID. Choose a custom Topic ID or a new Topic ID.') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Topic Name.') ->param('description', '', new Text(2048), 'Topic Description.', true) - ->inject('user') ->inject('dbForProject') ->inject('response') - ->action(function (string $id, string $providerId, string $name, string $description, Document $user, Database $dbForProject, Response $response) { + ->action(function (string $topicId, string $providerId, string $name, string $description, Database $dbForProject, Response $response) { + $topicId = $topicId == 'unique()' ? ID::unique() : $topicId; $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1029,16 +1029,10 @@ App::post('/v1/messaging/topics') } $topic = new Document([ - '$id' => $id, - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], + '$id' => $topicId, 'providerId' => $providerId, 'providerInternalId' => $provider->getInternalId(), 'name' => $name, - ]); if ($description) { @@ -1052,26 +1046,26 @@ App::post('/v1/messaging/topics') ->dynamic($topic, Response::MODEL_TOPIC); }); -App::patch('/v1/messaging/topics/:id') +App::patch('/v1/messaging/topics/:topicId') ->desc('Update a topic.') ->groups(['api', 'messaging']) ->label('audits.event', 'topics.update') ->label('audits.resource', 'topics/{response.$id}') ->label('scope', 'topics.write') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTopic') ->label('sdk.description', '/docs/references/messaging/update-topic.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOPIC) - ->param('id', '', new UID(), 'Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID.') ->param('name', '', new Text(128), 'Topic Name.', true) ->param('description', null, new Text(128), 'Topic Description.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $id, string $name, string $description, Database $dbForProject, Response $response) { - $topic = $dbForProject->getDocument('topics', $id); + ->action(function (string $topicId, string $name, string $description, Database $dbForProject, Response $response) { + $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -1085,36 +1079,36 @@ App::patch('/v1/messaging/topics/:id') $topic->setAttribute('description', $description); } - $topic = $dbForProject->updateDocument('topics', $id, $topic); + $topic = $dbForProject->updateDocument('topics', $topicId, $topic); $response ->dynamic($topic, Response::MODEL_TOPIC); }); -App::delete('/v1/messaging/topics/:id') +App::delete('/v1/messaging/topics/:topicId') ->desc('Delete a topic.') ->groups(['api', 'messaging']) ->label('audits.event', 'topics.delete') - ->label('audits.resource', 'topics/{request.id}') + ->label('audits.resource', 'topics/{request.topicId}') ->label('scope', 'topics.write') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteTopic') ->label('sdk.description', '/docs/references/messaging/delete-topic.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('id', '', new UID(), 'Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID.') ->inject('dbForProject') ->inject('response') - ->action(function (string $id, Database $dbForProject, Response $response) { - $topic = $dbForProject->getDocument('topics', $id); + ->action(function (string $topicId, Database $dbForProject, Response $response) { + $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - $topic = $dbForProject->deleteDocument('topics', $id); + $topic = $dbForProject->deleteDocument('topics', $topicId); $response->noContent(); }); diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 2a1b0a8039..140c3b8137 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -87,6 +87,8 @@ trait ProjectCustom 'providers.write', 'messages.read', 'messages.write', + 'topics.write', + 'topics.read' ], ]); diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 8a0937c0eb..5303fbb5c9 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -6,7 +6,7 @@ use Tests\E2E\Client; trait MessagingBase { - public function testCreateProviders(): array + public function testCreateProviders(): array { $providersParams = [ 'sendgrid' => [ @@ -190,4 +190,90 @@ trait MessagingBase $this->assertEquals(204, $response['headers']['status-code']); } } -} \ No newline at end of file + + public function testCreateTopic(): string + { + $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'id' => 'unique()', + 'name' => 'Sengrid1', + 'apiKey' => 'my-apikey', + ]); + $this->assertEquals(201, $provider['headers']['status-code']); + $response = $this->client->call(Client::METHOD_POST, '/messaging/topics', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'providerId' => $provider['body']['$id'], + 'topicId' => 'unique()', + 'name' => 'my-app', + 'description' => 'web app' + ]); + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals('my-app', $response['body']['name']); + + return $response['body']['$id']; + } + + /** + * @depends testCreateTopic + */ + public function testUpdateTopic(string $topicId): string + { + $response = $this->client->call(Client::METHOD_PATCH, '/messaging/topics/' . $topicId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'name' => 'android-app', + 'description' => 'updated-description' + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('android-app', $response['body']['name']); + $this->assertEquals('updated-description', $response['body']['description']); + return $topicId; + } + + public function testListTopic() + { + $response = $this->client->call(Client::METHOD_GET, '/messaging/topics', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, \count($response['body']['topics'])); + } + + /** + * @depends testUpdateTopic + */ + public function testGetTopic(string $topicId) + { + $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' .$topicId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('android-app', $response['body']['name']); + $this->assertEquals('updated-description', $response['body']['description']); + } + + /** + * @depends testUpdateTopic + */ + public function testDeleteTopic(string $topicId) + { + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' .$topicId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + $this->assertEquals(204, $response['headers']['status-code']); + } +} diff --git a/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php b/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php new file mode 100644 index 0000000000..8250c0229a --- /dev/null +++ b/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php @@ -0,0 +1,15 @@ + Date: Fri, 25 Aug 2023 14:40:48 +0530 Subject: [PATCH 03/18] fixes test --- tests/e2e/Services/Messaging/MessagingBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 1fac9b8f7e..bc675a9603 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -198,7 +198,7 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ - 'id' => 'unique()', + 'providerId' => 'unique()', 'name' => 'Sengrid1', 'apiKey' => 'my-apikey', ]); From 264f0bd5ddd93124df0c6877dfa0b51c57cf78c3 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Mon, 28 Aug 2023 20:41:05 +0530 Subject: [PATCH 04/18] adds subscriber controllers --- app/config/errors.php | 19 +- app/config/roles.php | 4 +- app/controllers/api/messaging.php | 181 +++++++++++++++++- src/Appwrite/Extend/Exception.php | 5 + tests/e2e/Scopes/ProjectCustom.php | 4 +- .../e2e/Services/Messaging/MessagingBase.php | 82 +++++++- 6 files changed, 276 insertions(+), 19 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 97b5f0661c..98c2918146 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -712,7 +712,24 @@ return [ /** Topic Errors */ Exception::TOPIC_NOT_FOUND => [ 'name' => Exception::TOPIC_NOT_FOUND, - 'description' => 'Provider with the request ID could not be found.', + 'description' => 'Topic with the request ID could not be found.', 'code' => 404, ], + Exception::TOPIC_ALREADY_EXISTS => [ + 'name' => Exception::TOPIC_ALREADY_EXISTS, + 'description' => 'Topic with the request ID already exists.', + 'code' => 409, + ], + + /** Subscriber Errors */ + Exception::SUBSCRIBER_NOT_FOUND => [ + 'name' => Exception::SUBSCRIBER_NOT_FOUND, + 'description' => 'Subscriber with the request ID could not be found.', + 'code' => 404, + ], + Exception::SUBSCRIBER_ALREADY_EXISTS => [ + 'name' => Exception::SUBSCRIBER_ALREADY_EXISTS, + 'description' => 'Subscriber with the request ID already exists.', + 'code' => 409, + ], ]; diff --git a/app/config/roles.php b/app/config/roles.php index 4666268d0c..30dbdd528d 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -27,7 +27,9 @@ $member = [ 'messages.write', 'messages.read', 'topics.write', - 'topics.read' + 'topics.read', + 'subscribers.write', + 'subscribers.read' ]; $admins = [ diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index a4cd2d8ef9..766641a852 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -9,6 +9,7 @@ use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -974,7 +975,7 @@ App::get('/v1/messaging/topics') ]), Response::MODEL_TOPIC_LIST); }); -App::get('/v1/messaging/topics/:id') +App::get('/v1/messaging/topics/:topicId') ->desc('Get a topic.') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -985,17 +986,17 @@ App::get('/v1/messaging/topics/:id') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOPIC) - ->param('id', '', new UID(), 'Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID.') ->inject('dbForProject') ->inject('response') - ->action(function (string $id, Database $dbForProject, Response $response) { - $topic = $dbForProject->getDocument('topics', $id); + ->action(function (string $topicId, Database $dbForProject, Response $response) { + $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - $topic = $dbForProject->getDocument('topics', $id); + $topic = $dbForProject->getDocument('topics', $topicId); $response ->dynamic($topic, Response::MODEL_TOPIC); @@ -1039,11 +1040,14 @@ App::post('/v1/messaging/topics') $topic->setAttribute('description', $description); } - $topic = $dbForProject->createDocument('topics', $topic); - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($topic, Response::MODEL_TOPIC); + try{ + $topic = $dbForProject->createDocument('topics', $topic); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($topic, Response::MODEL_TOPIC); + } catch(DuplicateException) { + throw new Exception(Exception::TOPIC_ALREADY_EXISTS); + } }); App::patch('/v1/messaging/topics/:topicId') @@ -1112,6 +1116,163 @@ App::delete('/v1/messaging/topics/:topicId') $response->noContent(); }); +App::get('/v1/messaging/topics/:topicId/subscribers') + ->desc('List topic\'s subscribers.') + ->groups(['api', 'messaging']) + ->label('scope', 'subscribers.read') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'listSubscribers') + ->label('sdk.description', '/docs/references/messaging/list-subscribers.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_SUBSCRIBER_LIST) + ->param('topicId', '', new UID(), 'Topic ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $topicId, Database $dbForProject, Response $response) { + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + $subscribers = $dbForProject->find('subscribers', [ + Query::equal('topicInternalId', [$topic->getInternalId()]) + ]); + + $response + ->dynamic(new Document([ + 'subscribers' => $subscribers, + 'total' => \count($subscribers), + ]), Response::MODEL_SUBSCRIBER_LIST); + }); + +App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') + ->desc('Get a topic\'s subscriber.') + ->groups(['api', 'messaging']) + ->label('scope', 'subscribers.read') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'getSubscriber') + ->label('sdk.description', '/docs/references/messaging/get-subscriber.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_SUBSCRIBER) + ->param('topicId', '', new UID(), 'Topic ID.') + ->param('subscriberId', '', new UID(), 'Subscriber ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) { + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + $subscriber = $dbForProject->getDocument('subscribers', $subscriberId); + + if ($subscriber->isEmpty() || $subscriber->getAttribute('topicId')!==$topicId) { + throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); + } + + $response + ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); + }); + +App::post('/v1/messaging/topics/:topicId/subscribers') + ->desc('Adds a Subscriber to a Topic.') + ->groups(['api', 'messaging']) + ->label('audits.event', 'subscribers.create') + ->label('audits.resource', 'subscribers/{response.$id}') + ->label('scope', 'subscribers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'addSubscriber') + ->label('sdk.description', '/docs/references/messaging/add-subscriber.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_SUBSCRIBER) + ->param('subscriberId', '', new CustomId(), 'Subscriber ID. Choose a custom Topic ID or a new Topic ID.') + ->param('topicId', '', new UID(), 'Topic ID.') + ->param('targetId', '', new UID(), 'Target ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $subscriberId, string $topicId, string $targetId, Database $dbForProject, Response $response) { + $subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId; + + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + $target = $dbForProject->getDocument('targets', $targetId); + + if ($target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_NOT_FOUND); + } + + $subscriber = new Document([ + '$id' => $subscriberId, + '$permissions' => [ + Permission::read(Role::user($target->getAttribute('userId'))), + Permission::delete(Role::user($target->getAttribute('userId'))), + ], + 'topicId' => $topicId, + 'topicInternalId' => $topic->getInternalId(), + 'targetId' => $targetId, + 'targetInternalId' => $target->getInternalId(), + ]); + + try { + $subscriber = $dbForProject->createDocument('subscribers', $subscriber); + $dbForProject->deleteCachedDocument('topics', $topicId); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); + } catch(DuplicateException) { + throw new Exception(Exception::SUBSCRIBER_ALREADY_EXISTS); + } + }); + +App::delete('/v1/messaging/topics/:topicId/subscriber/:subscriberId') + ->desc('Delete a Subscriber from a Topic.') + ->groups(['api', 'messaging']) + ->label('audits.event', 'subscribers.delete') + ->label('audits.resource', 'subscribers/{request.subscriberId}') + ->label('scope', 'subscribers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'deleteSubscriber') + ->label('sdk.description', '/docs/references/messaging/delete-subscriber.md') + ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('topicId', '', new UID(), 'Topic ID.') + ->param('subscriberId', '', new UID(), 'Subscriber ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) { + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + + if ($topic->isEmpty()) { + throw new Exception(Exception::TOPIC_NOT_FOUND); + } + + $subscriber = $dbForProject->getDocument('subscribers', $subscriberId); + + if ($subscriber->isEmpty() || $subscriber->getAttribute('topicId') !== $topicId) { + throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); + } + $subscriber = $dbForProject->deleteDocument('subscribers', $subscriberId); + $dbForProject->deleteCachedDocument('topics', $topicId); + + $response + ->setStatusCode(Response::STATUS_CODE_NOCONTENT) + ->noContent(); + }); + App::post('/v1/messaging/messages/email') ->desc('Send an email.') ->groups(['api', 'messaging']) diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 4b5cac24a8..46a362406b 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -219,6 +219,11 @@ class Exception extends \Exception /** Topic */ public const TOPIC_NOT_FOUND = 'topic_not_found'; + public const TOPIC_ALREADY_EXISTS = 'topic_already_exists'; + + /** Subscriber */ + public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found'; + public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists'; protected $type = ''; protected $errors = []; diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 140c3b8137..668a0f086f 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -88,7 +88,9 @@ trait ProjectCustom 'messages.read', 'messages.write', 'topics.write', - 'topics.read' + 'topics.read', + 'subscribers.write', + 'subscribers.read' ], ]); diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index bc675a9603..9a11e257eb 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Messaging; use Tests\E2E\Client; +use Utopia\Database\Helpers\ID; trait MessagingBase { @@ -191,7 +192,7 @@ trait MessagingBase } } - public function testCreateTopic(): string + public function testCreateTopic(): array { $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', \array_merge([ 'content-type' => 'application/json', @@ -199,7 +200,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ 'providerId' => 'unique()', - 'name' => 'Sengrid1', + 'name' => 'Sendgrid1', 'apiKey' => 'my-apikey', ]); $this->assertEquals(201, $provider['headers']['status-code']); @@ -216,15 +217,15 @@ trait MessagingBase $this->assertEquals(201, $response['headers']['status-code']); $this->assertEquals('my-app', $response['body']['name']); - return $response['body']['$id']; + return $response['body']; } /** * @depends testCreateTopic */ - public function testUpdateTopic(string $topicId): string + public function testUpdateTopic(array $topic): string { - $response = $this->client->call(Client::METHOD_PATCH, '/messaging/topics/' . $topicId, [ + $response = $this->client->call(Client::METHOD_PATCH, '/messaging/topics/' . $topic['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -235,7 +236,7 @@ trait MessagingBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('android-app', $response['body']['name']); $this->assertEquals('updated-description', $response['body']['description']); - return $topicId; + return $response['body']['$id']; } public function testListTopic() @@ -264,6 +265,75 @@ trait MessagingBase $this->assertEquals('updated-description', $response['body']['description']); } + /** + * @depends testCreateTopic + */ + public function testCreateSubscriber (array $topic) { + $userId = $this->getUser()['$id']; + $target = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/targets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'targetId' => ID::unique(), + 'providerId' => $topic['providerId'], + 'identifier' => 'my-token', + ]); + $this->assertEquals(201, $target['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['$id'] . '/subscribers', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'subscriberId' => 'unique()', + 'targetId' => $target['body']['$id'], + ]); + $this->assertEquals(201, $response['headers']['status-code']); + return [ + 'topicId' => $topic['$id'], + 'targetId' => $target['body']['$id'], + 'subscriberId' => $response['body']['$id'] + ]; + } + + /** + * @depends testCreateSubscriber + */ + public function testGetSubscriber(array $data) { + $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscriber/' . $data['subscriberId'], \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($data['topicId'], $response['body']['topicId']); + $this->assertEquals($data['targetId'], $response['body']['targetId']); + } + + /** + * @depends testCreateSubscriber + */ + public function testListSubscribers(array $data) { + $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscribers', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + $this->assertEquals(\count($response['body']['subscribers']), $response['body']['total']); + } + + /** + * @depends testCreateSubscriber + */ + public function testDeleteSubscriber(array $data) + { + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $data['topicId'] .'/subscriber/' .$data['subscriberId'], \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + $this->assertEquals(204, $response['headers']['status-code']); + } + /** * @depends testUpdateTopic */ From 1d6276eb9fb8e7516fd0be22ef442e581181ff96 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Mon, 28 Aug 2023 20:43:18 +0530 Subject: [PATCH 05/18] lint fix --- app/controllers/api/messaging.php | 16 ++++++++-------- tests/e2e/Services/Messaging/MessagingBase.php | 15 +++++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 766641a852..049453324d 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1040,12 +1040,12 @@ App::post('/v1/messaging/topics') $topic->setAttribute('description', $description); } - try{ + try { $topic = $dbForProject->createDocument('topics', $topic); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($topic, Response::MODEL_TOPIC); - } catch(DuplicateException) { + } catch (DuplicateException) { throw new Exception(Exception::TOPIC_ALREADY_EXISTS); } }); @@ -1140,13 +1140,13 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $subscribers = $dbForProject->find('subscribers', [ Query::equal('topicInternalId', [$topic->getInternalId()]) ]); - + $response ->dynamic(new Document([ 'subscribers' => $subscribers, 'total' => \count($subscribers), ]), Response::MODEL_SUBSCRIBER_LIST); - }); + }); App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') ->desc('Get a topic\'s subscriber.') @@ -1169,10 +1169,10 @@ App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - + $subscriber = $dbForProject->getDocument('subscribers', $subscriberId); - if ($subscriber->isEmpty() || $subscriber->getAttribute('topicId')!==$topicId) { + if ($subscriber->isEmpty() || $subscriber->getAttribute('topicId') !== $topicId) { throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); } @@ -1200,7 +1200,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->inject('response') ->action(function (string $subscriberId, string $topicId, string $targetId, Database $dbForProject, Response $response) { $subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId; - + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { @@ -1231,7 +1231,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); - } catch(DuplicateException) { + } catch (DuplicateException) { throw new Exception(Exception::SUBSCRIBER_ALREADY_EXISTS); } }); diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 9a11e257eb..b97557d3ec 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -268,7 +268,8 @@ trait MessagingBase /** * @depends testCreateTopic */ - public function testCreateSubscriber (array $topic) { + public function testCreateSubscriber(array $topic) + { $userId = $this->getUser()['$id']; $target = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/targets', array_merge([ 'content-type' => 'application/json', @@ -290,8 +291,8 @@ trait MessagingBase ]); $this->assertEquals(201, $response['headers']['status-code']); return [ - 'topicId' => $topic['$id'], - 'targetId' => $target['body']['$id'], + 'topicId' => $topic['$id'], + 'targetId' => $target['body']['$id'], 'subscriberId' => $response['body']['$id'] ]; } @@ -299,7 +300,8 @@ trait MessagingBase /** * @depends testCreateSubscriber */ - public function testGetSubscriber(array $data) { + public function testGetSubscriber(array $data) + { $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscriber/' . $data['subscriberId'], \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -312,7 +314,8 @@ trait MessagingBase /** * @depends testCreateSubscriber */ - public function testListSubscribers(array $data) { + public function testListSubscribers(array $data) + { $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscribers', \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -327,7 +330,7 @@ trait MessagingBase */ public function testDeleteSubscriber(array $data) { - $response = $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $data['topicId'] .'/subscriber/' .$data['subscriberId'], \array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $data['topicId'] . '/subscriber/' . $data['subscriberId'], \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); From c2c70d68e9b624e22adc79ff960dd1a36962fd92 Mon Sep 17 00:00:00 2001 From: wess Date: Mon, 28 Aug 2023 16:27:55 -0400 Subject: [PATCH 06/18] Working on messaging tests with worker --- app/workers/messaging.php | 46 +++++++++------ composer.lock | 56 +++++++++---------- docker-compose.yml | 14 ++--- phpunit.xml | 2 +- .../Account/AccountCustomClientTest.php | 11 ++++ 5 files changed, 75 insertions(+), 54 deletions(-) diff --git a/app/workers/messaging.php b/app/workers/messaging.php index 84af6fa802..1a58029c07 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -79,11 +79,25 @@ class MessagingV1 extends Worker public function run(): void { - $providerId = $this->args['providerId']; - $providerRecord = - $this - ->getConsoleDB() - ->getDocument('providers', $providerId); + $messageId = $this->args['messageId']; + $messageRecord = + $this + ->getConsoleDB() + ->getDocument('messages', $messageId); + + $providerId = $messageRecord['providerId']; + + $providerRecord = + $this + ->getConsoleDB() + ->getDocument('providers', $providerId); + + $message = match ($providerRecord->getAttribute('type')) { + 'sms' => $this->buildSMSMessage($messageRecord->getArrayCopy()), + 'push' => $this->buildPushMessage($messageRecord->getArrayCopy()), + 'email' => $this->buildEmailMessage($messageRecord->getArrayCopy()), + default => null + }; $provider = match ($providerRecord->getAttribute('type')) {//stubbbbbbed. 'sms' => $this->sms($providerRecord), @@ -92,25 +106,21 @@ class MessagingV1 extends Worker default => null }; + var_dump($provider); + die; + // Query for the provider // switch on provider name // call function passing needed credentials returns required provider. - $messageId = $this->args['messageId']; - $messageRecord = - $this - ->getConsoleDB() - ->getDocument('messages', $messageId); - - $message = match ($providerRecord->getAttribute('type')) { - 'sms' => $this->buildSMSMessage($messageRecord->getArrayCopy()), - 'push' => $this->buildPushMessage($messageRecord->getArrayCopy()), - 'email' => $this->buildEmailMessage($messageRecord->getArrayCopy()), - default => null - }; - $provider->send($message); + try { + $provider->send($message); + } catch (\Exception $error) { + throw new Exception('Error sending message: ' . $error->getMessage(), 500); + } + } public function shutdown(): void diff --git a/composer.lock b/composer.lock index f2b39d8414..ca4eff1d7d 100644 --- a/composer.lock +++ b/composer.lock @@ -1183,16 +1183,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -1201,7 +1201,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1246,7 +1246,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -1262,7 +1262,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "utopia-php/abuse", @@ -5024,16 +5024,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -5048,7 +5048,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5086,7 +5086,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -5102,20 +5102,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -5130,7 +5130,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5169,7 +5169,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -5185,7 +5185,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "textalk/websocket", @@ -5288,16 +5288,16 @@ }, { "name": "twig/twig", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "5cf942bbab3df42afa918caeba947f1b690af64b" + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b", - "reference": "5cf942bbab3df42afa918caeba947f1b690af64b", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", "shasum": "" }, "require": { @@ -5307,7 +5307,7 @@ }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4.9|^6.3" }, "type": "library", "autoload": { @@ -5343,7 +5343,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.0" + "source": "https://github.com/twigphp/Twig/tree/v3.7.1" }, "funding": [ { @@ -5355,7 +5355,7 @@ "type": "tidelift" } ], - "time": "2023-07-26T07:16:09+00:00" + "time": "2023-08-28T11:09:02+00:00" } ], "aliases": [ diff --git a/docker-compose.yml b/docker-compose.yml index 16b5937704..79c7abd7fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -732,13 +732,13 @@ services: - _APP_CONNECTIONS_QUEUE - _APP_REGION - appwrite-assistant: - container_name: appwrite-assistant - image: appwrite/assistant:0.1.0 - networks: - - appwrite - environment: - - OPENAI_API_KEY + # appwrite-assistant: + # container_name: appwrite-assistant + # image: appwrite/assistant:0.1.0 + # networks: + # - appwrite + # environment: + # - OPENAI_API_KEY openruntimes-executor: container_name: openruntimes-executor diff --git a/phpunit.xml b/phpunit.xml index cffe166336..cca546e9ad 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index d225496a65..796c723ee6 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -12,6 +12,7 @@ use Tests\E2E\Scopes\SideClient; use Utopia\Database\DateTime; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Messaging\Adapters\SMS\Mock as MockProvider; use function sleep; @@ -767,6 +768,16 @@ class AccountCustomClientTest extends Scope } + public function testCreateMockSMS(): MockProvider + { + + $smsMockProvider = new MockProvider('blah', 'blah'); + + $this->assertNotEquals(null, $smsMockProvider); + + return $smsMockProvider; + } + public function testCreatePhone(): array { $number = '+123456789'; From e53af019da46c367546ed6bc789f28143a9ff7f2 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Tue, 29 Aug 2023 21:43:21 +0530 Subject: [PATCH 07/18] Revert "Working on messaging tests with worker" This reverts commit c2c70d68e9b624e22adc79ff960dd1a36962fd92. --- app/workers/messaging.php | 46 ++++++--------- composer.lock | 56 +++++++++---------- docker-compose.yml | 14 ++--- phpunit.xml | 2 +- .../Account/AccountCustomClientTest.php | 11 ---- 5 files changed, 54 insertions(+), 75 deletions(-) diff --git a/app/workers/messaging.php b/app/workers/messaging.php index 1a58029c07..84af6fa802 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -79,25 +79,11 @@ class MessagingV1 extends Worker public function run(): void { - $messageId = $this->args['messageId']; - $messageRecord = - $this - ->getConsoleDB() - ->getDocument('messages', $messageId); - - $providerId = $messageRecord['providerId']; - - $providerRecord = - $this - ->getConsoleDB() - ->getDocument('providers', $providerId); - - $message = match ($providerRecord->getAttribute('type')) { - 'sms' => $this->buildSMSMessage($messageRecord->getArrayCopy()), - 'push' => $this->buildPushMessage($messageRecord->getArrayCopy()), - 'email' => $this->buildEmailMessage($messageRecord->getArrayCopy()), - default => null - }; + $providerId = $this->args['providerId']; + $providerRecord = + $this + ->getConsoleDB() + ->getDocument('providers', $providerId); $provider = match ($providerRecord->getAttribute('type')) {//stubbbbbbed. 'sms' => $this->sms($providerRecord), @@ -106,21 +92,25 @@ class MessagingV1 extends Worker default => null }; - var_dump($provider); - die; - // Query for the provider // switch on provider name // call function passing needed credentials returns required provider. + $messageId = $this->args['messageId']; + $messageRecord = + $this + ->getConsoleDB() + ->getDocument('messages', $messageId); + + $message = match ($providerRecord->getAttribute('type')) { + 'sms' => $this->buildSMSMessage($messageRecord->getArrayCopy()), + 'push' => $this->buildPushMessage($messageRecord->getArrayCopy()), + 'email' => $this->buildEmailMessage($messageRecord->getArrayCopy()), + default => null + }; - try { - $provider->send($message); - } catch (\Exception $error) { - throw new Exception('Error sending message: ' . $error->getMessage(), 500); - } - + $provider->send($message); } public function shutdown(): void diff --git a/composer.lock b/composer.lock index ca4eff1d7d..f2b39d8414 100644 --- a/composer.lock +++ b/composer.lock @@ -1183,16 +1183,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { @@ -1201,7 +1201,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1246,7 +1246,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" }, "funding": [ { @@ -1262,7 +1262,7 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "utopia-php/abuse", @@ -5024,16 +5024,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "shasum": "" }, "require": { @@ -5048,7 +5048,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5086,7 +5086,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" }, "funding": [ { @@ -5102,20 +5102,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -5130,7 +5130,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5169,7 +5169,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -5185,7 +5185,7 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "textalk/websocket", @@ -5288,16 +5288,16 @@ }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "5cf942bbab3df42afa918caeba947f1b690af64b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b", + "reference": "5cf942bbab3df42afa918caeba947f1b690af64b", "shasum": "" }, "require": { @@ -5307,7 +5307,7 @@ }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", "autoload": { @@ -5343,7 +5343,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.7.0" }, "funding": [ { @@ -5355,7 +5355,7 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-07-26T07:16:09+00:00" } ], "aliases": [ diff --git a/docker-compose.yml b/docker-compose.yml index 79c7abd7fa..16b5937704 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -732,13 +732,13 @@ services: - _APP_CONNECTIONS_QUEUE - _APP_REGION - # appwrite-assistant: - # container_name: appwrite-assistant - # image: appwrite/assistant:0.1.0 - # networks: - # - appwrite - # environment: - # - OPENAI_API_KEY + appwrite-assistant: + container_name: appwrite-assistant + image: appwrite/assistant:0.1.0 + networks: + - appwrite + environment: + - OPENAI_API_KEY openruntimes-executor: container_name: openruntimes-executor diff --git a/phpunit.xml b/phpunit.xml index cca546e9ad..cffe166336 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 796c723ee6..d225496a65 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -12,7 +12,6 @@ use Tests\E2E\Scopes\SideClient; use Utopia\Database\DateTime; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\Messaging\Adapters\SMS\Mock as MockProvider; use function sleep; @@ -768,16 +767,6 @@ class AccountCustomClientTest extends Scope } - public function testCreateMockSMS(): MockProvider - { - - $smsMockProvider = new MockProvider('blah', 'blah'); - - $this->assertNotEquals(null, $smsMockProvider); - - return $smsMockProvider; - } - public function testCreatePhone(): array { $number = '+123456789'; From 39854239fb7202f6d7932714189ab682ecf58b38 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Thu, 21 Sep 2023 02:29:57 +0530 Subject: [PATCH 08/18] skip authorization in adding subscriber on targets --- 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 17e7d18d5b..13710bca0a 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1568,7 +1568,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') throw new Exception(Exception::TOPIC_NOT_FOUND); } - $target = $dbForProject->getDocument('targets', $targetId); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); From 7708779ef15965f4e30231e45251d5e38ce0cee0 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Wed, 27 Sep 2023 19:08:35 +0530 Subject: [PATCH 09/18] adds email endpoint test --- .../e2e/Services/Messaging/MessagingBase.php | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 3a131845a6..5654f01257 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Messaging; use Tests\E2E\Client; +use Utopia\App; use Utopia\Database\Helpers\ID; trait MessagingBase @@ -366,4 +367,99 @@ trait MessagingBase ]); $this->assertEquals(204, $response['headers']['status-code']); } + + public function testSendEmail() + { + + $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_RECEIVER_EMAIL'); + $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_FROM'); + $apiKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_API_KEY'); + $domain = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_DOMAIN'); + $isEuRegion = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_IS_EU_REGION'); + if ($to === '' || $from === '' || $apiKey === '' || $domain === '' || $isEuRegion === '') { + $this->markTestSkipped('Email provider not configured'); + } + + // Create provider + $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/mailgun', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'providerId' => 'unique()', + 'name' => 'Mailgun-provider', + 'apiKey' => $apiKey, + 'domain' => $domain, + 'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), + 'from' => $from + ]); + $this->assertEquals(201, $provider['headers']['status-code']); + + // Create Topic + $topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'providerId' => $provider['body']['$id'], + 'topicId' => 'unique()', + 'name' => 'topic1', + 'description' => 'Test Topic' + ]); + $this->assertEquals(201, $topic['headers']['status-code']); + + // Create User + $user = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'userId' => ID::custom('test-user'), + 'email' => 'prateekbanga12@gmail.com', + 'password' => 'password', + 'name' => 'Messaging User', + ], false); + + $this->assertEquals(201, $user['headers']['status-code']); + + // Create Target + $target = $this->client->call(Client::METHOD_POST, '/users/test-user/targets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'targetId' => ID::unique(), + 'providerId' => $provider['body']['$id'], + 'identifier' => $to, + ]); + + $this->assertEquals(201, $target['headers']['status-code']); + + // Create Subscriber + $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'subscriberId' => 'unique()', + 'targetId' => $target['body']['$id'], + ]); + + $this->assertEquals(201, $subscriber['headers']['status-code']); + + // Create Email + $email = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'messageId' => 'unique()', + 'providerId' => $provider['body']['$id'], + 'to' => [$target['body']['$id']], + 'subject' => 'Khali beats Undertaker', + 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + ]); + + $this->assertEquals(201, $email['headers']['status-code']); + + } } From 1a50b2948f9a9026bd29620df78d6bd3046e5799 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Wed, 27 Sep 2023 19:10:56 +0530 Subject: [PATCH 10/18] lint fix --- tests/e2e/Services/Messaging/MessagingBase.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 5654f01257..97a5ad7d1d 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -370,7 +370,7 @@ trait MessagingBase public function testSendEmail() { - + $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_RECEIVER_EMAIL'); $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_FROM'); $apiKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_API_KEY'); @@ -460,6 +460,5 @@ trait MessagingBase ]); $this->assertEquals(201, $email['headers']['status-code']); - } } From 0b1b7646d5677c7c085ba5d1784c9b6503374501 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Fri, 13 Oct 2023 18:26:54 +0530 Subject: [PATCH 11/18] review changes --- app/controllers/api/messaging.php | 63 ++++++++++++------- src/Appwrite/Event/Messaging.php | 37 ++++++++++- .../Database/Validator/Queries/Messages.php | 25 ++++++++ .../Database/Validator/Queries/Providers.php | 4 +- 4 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Messages.php diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 058f3ce1f6..40e6a159bc 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -5,6 +5,7 @@ use Appwrite\Extend\Exception; use Appwrite\Permission; use Appwrite\Role; use Appwrite\Utopia\Database\Validator\CustomId; +use Appwrite\Utopia\Database\Validator\Queries\Messages; use Appwrite\Utopia\Database\Validator\Queries\Providers; use Appwrite\Utopia\Database\Validator\Queries\Topics; use Appwrite\Utopia\Response; @@ -19,10 +20,8 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\UID; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; -use Utopia\Validator\JSON; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; -use Utopia\Database\DateTime; App::post('/v1/messaging/providers/mailgun') ->desc('Create Mailgun Provider') @@ -1236,12 +1235,13 @@ App::post('/v1/messaging/topics') try { $topic = $dbForProject->createDocument('topics', $topic); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($topic, Response::MODEL_TOPIC); } catch (DuplicateException) { throw new Exception(Exception::TOPIC_ALREADY_EXISTS); } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($topic, Response::MODEL_TOPIC); }); App::get('/v1/messaging/topics') @@ -1328,7 +1328,7 @@ App::patch('/v1/messaging/topics/:topicId') ->label('sdk.response.model', Response::MODEL_TOPIC) ->param('topicId', '', new UID(), 'Topic ID.') ->param('name', '', new Text(128), 'Topic Name.', true) - ->param('description', null, new Text(128), 'Topic Description.', true) + ->param('description', null, new Text(2048), 'Topic Description.', true) ->inject('dbForProject') ->inject('response') ->action(function (string $topicId, string $name, string $description, Database $dbForProject, Response $response) { @@ -1387,8 +1387,8 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->label('scope', 'subscribers.write') ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'addSubscriber') - ->label('sdk.description', '/docs/references/messaging/add-subscriber.md') + ->label('sdk.method', 'createSubscriber') + ->label('sdk.description', '/docs/references/messaging/create-subscriber.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SUBSCRIBER) @@ -1427,19 +1427,20 @@ App::post('/v1/messaging/topics/:topicId/subscribers') try { $subscriber = $dbForProject->createDocument('subscribers', $subscriber); $dbForProject->deleteCachedDocument('topics', $topicId); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); } catch (DuplicateException) { throw new Exception(Exception::SUBSCRIBER_ALREADY_EXISTS); } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); App::get('/v1/messaging/topics/:topicId/subscribers') ->desc('List topic\'s subscribers.') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscribers') ->label('sdk.description', '/docs/references/messaging/list-subscribers.md') @@ -1471,7 +1472,7 @@ App::get('/v1/messaging/topics/:topicId/subscriber/:subscriberId') ->desc('Get a topic\'s subscriber.') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') - ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getSubscriber') ->label('sdk.description', '/docs/references/messaging/get-subscriber.md') @@ -1537,15 +1538,15 @@ App::delete('/v1/messaging/topics/:topicId/subscriber/:subscriberId') }); App::post('/v1/messaging/messages/email') - ->desc('Send an email.') + ->desc('Create an email.') ->groups(['api', 'messaging']) ->label('audits.event', 'messages.create') ->label('audits.resource', 'messages/{response.$id}') ->label('scope', 'messages.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') - ->label('sdk.method', 'sendEmail') - ->label('sdk.description', '/docs/references/messaging/send-email.md') + ->label('sdk.method', 'createEmail') + ->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) ->label('sdk.response.model', Response::MODEL_MESSAGE) @@ -1557,11 +1558,12 @@ App::post('/v1/messaging/messages/email') ->param('content', '', new Text(64230), 'Email Content.') ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) + ->param('deliveryTime', null, new DatetimeValidator(), 'Delivery time for message.', true) ->inject('dbForProject') ->inject('project') ->inject('messaging') ->inject('response') - ->action(function (string $messageId, string $providerId, array $to, string $subject, string $description, string $content, string $status, bool $html, Database $dbForProject, Document $project, Messaging $messaging, Response $response) { + ->action(function (string $messageId, string $providerId, array $to, string $subject, string $description, string $content, string $status, bool $html, ?string $deliveryTime, Database $dbForProject, Document $project, Messaging $messaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; $provider = $dbForProject->getDocument('providers', $providerId); @@ -1588,8 +1590,15 @@ App::post('/v1/messaging/messages/email') if ($status === 'processing') { $messaging ->setMessageId($message->getId()) - ->setProject($project) - ->trigger(); + ->setProject($project); + + if (!empty($deliveryTime)) { + $messaging + ->setDeliveryTime($deliveryTime) + ->schedule(); + } else { + $messaging->trigger(); + } } $response @@ -1608,7 +1617,7 @@ App::get('/v1/messaging/messages') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE_LIST) - ->param('queries', [], new Providers(), '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('queries', [], new Messages(), '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) ->inject('dbForProject') ->inject('response') ->action(function (array $queries, Database $dbForProject, Response $response) { @@ -1682,12 +1691,12 @@ App::patch('/v1/messaging/messages/email/:messageId') ->param('content', '', new Text(64230), 'Email Content.', true) ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) - ->param('deliveryTime', DateTime::now(), new DatetimeValidator(), 'Delivery time for message.', true) + ->param('deliveryTime', null, new DatetimeValidator(), 'Delivery time for message in ISO 8601 format.', true) ->inject('dbForProject') ->inject('project') ->inject('messaging') ->inject('response') - ->action(function (string $messageId, array $to, string $subject, string $description, string $content, string $status, bool $html, string $deliveryTime, Database $dbForProject, Document $project, Messaging $messaging, Response $response) { + ->action(function (string $messageId, array $to, string $subject, string $description, string $content, string $status, bool $html, ?string $deliveryTime, Database $dbForProject, Document $project, Messaging $messaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -1728,9 +1737,15 @@ App::patch('/v1/messaging/messages/email/:messageId') if ($status === 'processing') { $messaging ->setMessageId($message->getId()) + ->setProject($project); + + if (!empty($deliveryTime)) { + $messaging ->setDeliveryTime($deliveryTime) - ->setProject($project) - ->trigger(); + ->schedule(); + } else { + $messaging->trigger(); + } } $response diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index 18887ffcd3..45ef843caa 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -2,8 +2,9 @@ namespace Appwrite\Event; +use Resque; use ResqueScheduler; -use Utopia\Database\DateTime; +use Utopia\Database\Document; class Messaging extends Event { @@ -61,16 +62,46 @@ class Messaging extends Event return $this->deliveryTime; } + /** + * Set project for this event. + * + * @param Document $project + * @return self + */ + public function setProject(Document $project): self + { + $this->project = $project; + + return $this; + } + /** * Executes the event and sends it to the messaging worker. + * @return string|bool + * @throws \InvalidArgumentException */ public function trigger(): string | bool { - ResqueScheduler::enqueueAt(!empty($this->deliveryTime) ? $this->deliveryTime : DateTime::now(), $this->queue, $this->class, [ + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'messageId' => $this->messageId, + ]); + } + + /** + * Schedules the messaging event and schedules it in the messaging worker queue. + * + * @return void + * @throws \Resque_Exception + * @throws \ResqueScheduler_InvalidTimestampException + */ + public function schedule(): void + { + ResqueScheduler::enqueueAt(new \DateTime($this->deliveryTime, new \DateTimeZone('UTC')), $this->queue, $this->class, [ 'project' => $this->project, 'user' => $this->user, 'messageId' => $this->messageId, ]); - return true; } } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php new file mode 100644 index 0000000000..5fa391e7f6 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php @@ -0,0 +1,25 @@ + Date: Fri, 13 Oct 2023 18:35:49 +0530 Subject: [PATCH 12/18] adds already sent exception in update message --- app/config/errors.php | 5 +++++ app/controllers/api/messaging.php | 4 ++++ src/Appwrite/Extend/Exception.php | 1 + 3 files changed, 10 insertions(+) diff --git a/app/config/errors.php b/app/config/errors.php index 2c5dfbad01..5514e8e68e 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -785,5 +785,10 @@ return [ 'name' => Exception::MESSAGE_NOT_FOUND, 'description' => 'Message with the requested ID could not be found.', 'code' => 404, + ], + Exception::MESSAGE_ALREADY_SENT => [ + 'name' => Exception::MESSAGE_ALREADY_SENT, + 'description' => 'Message with the requested ID has already been sent.', + 'code' => 400, ] ]; diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 40e6a159bc..e83fc1114d 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1703,6 +1703,10 @@ App::patch('/v1/messaging/messages/email/:messageId') throw new Exception(Exception::MESSAGE_NOT_FOUND); } + if ($message->getAttribute('status') === 'sent') { + throw new Exception(Exception::MESSAGE_ALREADY_SENT); + } + if (\count($to) > 0) { $message->setAttribute('to', $to); } diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 861db5271c..ee17ccef0c 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -240,6 +240,7 @@ class Exception extends \Exception /** Message */ public const MESSAGE_NOT_FOUND = 'message_not_found'; + public const MESSAGE_ALREADY_SENT = 'message_already_sent'; protected $type = ''; protected $errors = []; From 4ccffb6649ece498d95a91f68127980aa1985187 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Tue, 17 Oct 2023 03:41:42 +0530 Subject: [PATCH 13/18] adds graphql tests for topics, subcribers, email --- .env | 10 +- app/controllers/api/messaging.php | 9 +- composer.lock | 6 +- docker-compose.yml | 10 +- tests/e2e/Services/GraphQL/Base.php | 191 +++++++++ tests/e2e/Services/GraphQL/MessagingTest.php | 402 ++++++++++++++++++ .../e2e/Services/Messaging/MessagingBase.php | 20 +- 7 files changed, 624 insertions(+), 24 deletions(-) diff --git a/.env b/.env index cb0ae9a424..aa90470729 100644 --- a/.env +++ b/.env @@ -103,8 +103,8 @@ _APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID= _APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY= _APP_MESSAGE_SMS_PROVIDER_MSG91_FROM= _APP_MESSAGE_SMS_PROVIDER_MSG91_TO= -_APP_MESSAGE_SMS_PROVIDER_MAILGUN_API_KEY= -_APP_MESSAGE_SMS_PROVIDER_MAILGUN_DOMAIN= -_APP_MESSAGE_SMS_PROVIDER_MAILGUN_FROM= -_APP_MESSAGE_SMS_PROVIDER_MAILGUN_RECEIVER_EMAIL= -_APP_MESSAGE_SMS_PROVIDER_MAILGUN_IS_EU_REGION= +_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY= +_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN= +_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM= +_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL= +_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION= diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index e83fc1114d..b3deaa409f 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1192,7 +1192,9 @@ App::delete('/v1/messaging/providers/:providerId') $dbForProject->deleteDocument('providers', $provider->getId()); - $response->noContent(); + $response + ->setStatusCode(Response::STATUS_CODE_NOCONTENT) + ->noContent(); }); App::post('/v1/messaging/topics') @@ -1376,7 +1378,10 @@ App::delete('/v1/messaging/topics/:topicId') } $topic = $dbForProject->deleteDocument('topics', $topicId); - $response->noContent(); + + $response + ->setStatusCode(Response::STATUS_CODE_NOCONTENT) + ->noContent(); }); App::post('/v1/messaging/topics/:topicId/subscribers') diff --git a/composer.lock b/composer.lock index 8abede5fa4..f80104986c 100644 --- a/composer.lock +++ b/composer.lock @@ -156,11 +156,11 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.13.0", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "5ab496b3908992b39275994a23783701c4b3de84" + "reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610" }, "require": { "php": ">=8.0", @@ -195,7 +195,7 @@ "php", "runtimes" ], - "time": "2023-09-12T19:38:43+00:00" + "time": "2023-10-16T15:39:53+00:00" }, { "name": "chillerlan/php-qrcode", diff --git a/docker-compose.yml b/docker-compose.yml index f0ddbfe41b..a365ce17ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -192,11 +192,11 @@ services: - _APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY - _APP_MESSAGE_SMS_PROVIDER_MSG91_FROM - _APP_MESSAGE_SMS_PROVIDER_MSG91_TO - - _APP_MESSAGE_SMS_PROVIDER_MAILGUN_API_KEY - - _APP_MESSAGE_SMS_PROVIDER_MAILGUN_DOMAIN - - _APP_MESSAGE_SMS_PROVIDER_MAILGUN_FROM - - _APP_MESSAGE_SMS_PROVIDER_MAILGUN_RECEIVER_EMAIL - - _APP_MESSAGE_SMS_PROVIDER_MAILGUN_IS_EU_REGION + - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY + - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN + - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM + - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL + - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION appwrite-realtime: entrypoint: realtime diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 0a93ac34c4..f3947c53b4 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -107,6 +107,11 @@ trait Base public static string $DELETE_USER_SESSIONS = 'delete_user_sessions'; public static string $DELETE_USER_SESSION = 'delete_user_session'; public static string $DELETE_USER = 'delete_user'; + public static string $CREATE_USER_TARGET = 'create_user_target'; + public static string $LIST_USER_TARGETS = 'list_user_targets'; + public static string $GET_USER_TARGET = 'get_user_target'; + public static string $UPDATE_USER_TARGET = 'update_user_target'; + public static string $DELETE_USER_TARGET = 'delete_user_target'; // Teams public static string $GET_TEAM = 'get_team'; @@ -220,6 +225,24 @@ trait Base public static string $UPDATE_APNS_PROVIDER = 'update_apns_provider'; public static string $DELETE_PROVIDER = 'delete_provider'; + // Topics + public static string $CREATE_TOPIC = 'create_topic'; + public static string $LIST_TOPICS = 'list_topics'; + public static string $GET_TOPIC = 'get_topic'; + public static string $UPDATE_TOPIC = 'update_topic'; + public static string $DELETE_TOPIC = 'delete_topic'; + + // Subscriptions + public static string $CREATE_SUBSCRIBER = 'create_subscriber'; + public static string $LIST_SUBSCRIBERS = 'list_subscribers'; + public static string $GET_SUBSCRIBER = 'get_subscriber'; + public static string $DELETE_SUBSCRIBER = 'delete_subscriber'; + + // Messages + public static string $CREATE_EMAIL = 'create_email'; + public static string $LIST_MESSAGES = 'list_messages'; + public static string $GET_MESSAGE = 'get_message'; + // Complex queries public static string $COMPLEX_QUERY = 'complex_query'; @@ -902,6 +925,51 @@ trait Base status } }'; + case self::$CREATE_USER_TARGET: + return 'mutation createUserTarget($userId: String!, $targetId: String!, $providerId: String!, $identifier: String!){ + usersCreateTarget(userId: $userId, targetId: $targetId, providerId: $providerId, identifier: $identifier) { + _id + userId + providerId + identifier + } + }'; + case self::$LIST_USER_TARGETS: + return 'query listUserTargets($userId: String!) { + usersListTargets(userId: $userId) { + total + targets { + _id + userId + providerId + identifier + } + } + }'; + case self::$GET_USER_TARGET: + return 'query getUserTarget($userId: String!, $targetId: String!) { + usersGetTarget(userId: $userId, targetId: $targetId) { + _id + userId + providerId + identifier + } + }'; + case self::$UPDATE_USER_TARGET: + return 'mutation updateUserTarget($userId: String!, $targetId: String!, $identifier: String!){ + usersUpdateTargetIdentifier(userId: $userId, targetId: $targetId, identifier: $identifier) { + _id + userId + providerId + identifier + } + }'; + case self::$DELETE_USER_TARGET: + return 'mutation deleteUserTarget($userId: String!, $targetId: String!){ + usersDeleteTarget(userId: $userId, targetId: $targetId) { + status + } + }'; case self::$GET_LOCALE: return 'query getLocale { localeGet { @@ -1938,6 +2006,129 @@ trait Base status } }'; + case self::$CREATE_TOPIC: + return 'mutation createTopic($providerId: String!, $topicId: String!, $name: String!, $description: String!) { + messagingCreateTopic(providerId: $providerId, topicId: $topicId, name: $name, description: $description) { + _id + name + providerId + description + } + }'; + case self::$LIST_TOPICS: + return 'query listTopics { + messagingListTopics { + total + topics { + _id + name + providerId + description + } + } + }'; + case self::$GET_TOPIC: + return 'query getTopic($topicId: String!) { + messagingGetTopic(topicId: $topicId) { + _id + name + providerId + description + } + }'; + case self::$UPDATE_TOPIC: + return 'mutation updateTopic($topicId: String!, $name: String!, $description: String!) { + messagingUpdateTopic(topicId: $topicId, name: $name, description: $description) { + _id + name + providerId + description + } + }'; + case self::$DELETE_TOPIC: + return 'mutation deleteTopic($topicId: String!) { + messagingDeleteTopic(topicId: $topicId) { + status + } + }'; + case self::$CREATE_SUBSCRIBER: + return 'mutation createSubscriber($subscriberId: String!, $targetId: String!, $topicId: String!) { + messagingCreateSubscriber(subscriberId: $subscriberId, targetId: $targetId, topicId: $topicId) { + _id + targetId + topicId + } + }'; + case self::$LIST_SUBSCRIBERS: + return 'query listSubscribers($topicId: String!) { + messagingListSubscribers(topicId: $topicId) { + total + subscribers { + _id + targetId + topicId + } + } + }'; + case self::$GET_SUBSCRIBER: + return 'query getSubscriber($topicId: String!, $subscriberId: String!) { + messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) { + _id + targetId + topicId + } + }'; + case self::$DELETE_SUBSCRIBER: + return 'mutation deleteSubscriber($topicId: String!, $subscriberId: String!) { + messagingDeleteSubscriber(topicId: $topicId, subscriberId: $subscriberId) { + status + } + }'; + case self::$CREATE_EMAIL: + return 'mutation createEmail($messageId: String!, $providerId: String!, $to: [String!]!, $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $deliveryTime: String) { + messagingCreateEmail(messageId: $messageId, providerId: $providerId, to: $to, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) { + _id + providerId + to + deliveryTime + deliveredAt + deliveryErrors + deliveredTo + status + description + } + }'; + case self::$LIST_MESSAGES: + return 'query listMessages { + messagingListMessages { + total + messages { + _id + providerId + to + deliveryTime + deliveredAt + deliveryErrors + deliveredTo + status + description + } + } + }'; + case self::$GET_MESSAGE: + return 'query getMessage($messageId: String!) { + messagingGetMessage(messageId: $messageId) { + _id + providerId + to + deliveryTime + deliveredAt + deliveryErrors + deliveredTo + status + description + } + }'; case self::$COMPLEX_QUERY: return 'mutation complex($databaseId: String!, $databaseName: String!, $collectionId: String!, $collectionName: String!, $documentSecurity: Boolean!, $collectionPermissions: [String!]!) { databasesCreate(databaseId: $databaseId, name: $databaseName) { diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 99bad52887..178b9a6a6c 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -6,6 +6,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\App; use Utopia\Database\Helpers\ID; class MessagingTest extends Scope @@ -257,4 +258,405 @@ class MessagingTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); } } + + public function testCreateTopic() + { + $providerParam = [ + 'sendgrid' => [ + 'providerId' => ID::unique(), + 'name' => 'Sengrid1', + 'apiKey' => 'my-apikey', + ] + ]; + $query = $this->getQuery(self::$CREATE_SENDGRID_PROVIDER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => $providerParam['sendgrid'], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $providerId = $response['body']['data']['messagingCreateSendgridProvider']['_id']; + + $query = $this->getQuery(self::$CREATE_TOPIC); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'providerId' => $providerId, + 'topicId' => ID::unique(), + 'name' => 'topic1', + 'description' => 'Active users', + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('topic1', $response['body']['data']['messagingCreateTopic']['name']); + $this->assertEquals('Active users', $response['body']['data']['messagingCreateTopic']['description']); + + return $response['body']['data']['messagingCreateTopic']; + } + + /** + * @depends testCreateTopic + */ + public function testUpdateTopic(array $topic) + { + $topicId = $topic['_id']; + $query = $this->getQuery(self::$UPDATE_TOPIC); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + 'name' => 'topic2', + 'description' => 'Inactive users', + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('topic2', $response['body']['data']['messagingUpdateTopic']['name']); + $this->assertEquals('Inactive users', $response['body']['data']['messagingUpdateTopic']['description']); + + return $topicId; + } + + /** + * @depends testCreateTopic + */ + public function testListTopics() + { + $query = $this->getQuery(self::$LIST_TOPICS); + $graphQLPayload = [ + 'query' => $query, + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, \count($response['body']['data']['messagingListTopics']['topics'])); + } + + /** + * @depends testUpdateTopic + */ + public function testGetTopic(string $topicId) + { + $query = $this->getQuery(self::$GET_TOPIC); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('topic2', $response['body']['data']['messagingGetTopic']['name']); + $this->assertEquals('Inactive users', $response['body']['data']['messagingGetTopic']['description']); + } + + /** + * @depends testCreateTopic + */ + public function testCreateSubscriber(array $topic) + { + $topicId = $topic['_id']; + + $userId = $this->getUser()['$id']; + + $query = $this->getQuery(self::$CREATE_USER_TARGET); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'targetId' => ID::unique(), + 'userId' => $userId, + 'providerId' => $topic['providerId'], + 'identifier' => 'token', + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['data']['usersCreateTarget']['userId']); + $this->assertEquals('token', $response['body']['data']['usersCreateTarget']['identifier']); + + $targetId = $response['body']['data']['usersCreateTarget']['_id']; + + $query = $this->getQuery(self::$CREATE_SUBSCRIBER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'subscriberId' => ID::unique(), + 'topicId' => $topicId, + 'targetId' => $targetId, + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + + return $response['body']['data']['messagingCreateSubscriber']; + } + + /** + * @depends testUpdateTopic + */ + public function testListSubscribers(string $topicId) + { + $query = $this->getQuery(self::$LIST_SUBSCRIBERS); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, \count($response['body']['data']['messagingListSubscribers']['subscribers'])); + } + + /** + * @depends testCreateSubscriber + */ + public function testGetSubscriber(array $subscriber) + { + $topicId = $subscriber['topicId']; + $subscriberId = $subscriber['_id']; + + $query = $this->getQuery(self::$GET_SUBSCRIBER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + 'subscriberId' => $subscriberId, + ], + ]; + + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($subscriberId, $response['body']['data']['messagingGetSubscriber']['_id']); + } + + /** + * @depends testCreateSubscriber + */ + public function testDeleteSubscriber(array $subscriber) + { + $topicId = $subscriber['topicId']; + $subscriberId = $subscriber['_id']; + + $query = $this->getQuery(self::$DELETE_SUBSCRIBER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + 'subscriberId' => $subscriberId, + ], + ]; + + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $response['headers']['status-code']); + } + + /** + * @depends testUpdateTopic + */ + public function testDeleteTopic(string $topicId) + { + $query = $this->getQuery(self::$DELETE_TOPIC); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + ], + ]; + $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(204, $response['headers']['status-code']); + } + + public function testSendEmail() + { + $to = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL'); + $from = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM'); + $apiKey = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY'); + $domain = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN'); + $isEuRegion = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION'); + if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { + $this->markTestSkipped('Email provider not configured'); + } + + $query = $this->getQuery(self::$CREATE_MAILGUN_PROVIDER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'providerId' => ID::unique(), + 'name' => 'Mailgun1', + 'apiKey' => $apiKey, + 'domain' => $domain, + 'from' => $from, + 'isEuRegion' => $isEuRegion, + ], + ]; + $provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $provider['headers']['status-code']); + + $providerId = $provider['body']['data']['messagingCreateMailgunProvider']['_id']; + + $query = $this->getQuery(self::$CREATE_TOPIC); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'providerId' => $providerId, + 'topicId' => ID::unique(), + 'name' => 'topic1', + 'description' => 'Active users', + ], + ]; + $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $topic['headers']['status-code']); + + $query = $this->getQuery(self::$CREATE_USER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'userId' => ID::custom('test-user'), + 'email' => $to, + 'password' => 'password', + 'name' => 'Messaging User', + ] + ]; + $user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $user['headers']['status-code']); + + $query = $this->getQuery(self::$CREATE_USER_TARGET); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'targetId' => ID::unique(), + 'userId' => $user['body']['data']['usersCreate']['_id'], + 'providerId' => $providerId, + 'identifier' => $to, + ], + ]; + $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $target['headers']['status-code']); + + $query = $this->getQuery(self::$CREATE_SUBSCRIBER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'subscriberId' => ID::unique(), + 'topicId' => $topic['body']['data']['messagingCreateTopic']['_id'], + 'targetId' => $target['body']['data']['usersCreateTarget']['_id'], + ], + ]; + $subscriber = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $subscriber['headers']['status-code']); + + $query = $this->getQuery(self::$CREATE_EMAIL); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'messageId' => ID::unique(), + 'providerId' => $providerId, + 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'subject' => 'Khali beats Undertaker', + 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + ], + ]; + $email = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $email['headers']['status-code']); + + \sleep(5); + + $query = $this->getQuery(self::$GET_MESSAGE); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'messageId' => $email['body']['data']['messagingCreateEmail']['_id'], + ], + ]; + $message = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $graphQLPayload); + + $this->assertEquals(200, $message['headers']['status-code']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); + } } diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 3f8039fae6..1c349a2dbe 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -323,7 +323,8 @@ trait MessagingBase $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscriber/' . $data['subscriberId'], \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($data['topicId'], $response['body']['topicId']); $this->assertEquals($data['targetId'], $response['body']['targetId']); @@ -337,7 +338,8 @@ trait MessagingBase $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $data['topicId'] . '/subscribers', \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, $response['body']['total']); $this->assertEquals(\count($response['body']['subscribers']), $response['body']['total']); @@ -371,11 +373,11 @@ trait MessagingBase public function testSendEmail() { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_RECEIVER_EMAIL'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_FROM'); - $apiKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_API_KEY'); - $domain = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_DOMAIN'); - $isEuRegion = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MAILGUN_IS_EU_REGION'); + $to = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL'); + $from = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM'); + $apiKey = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY'); + $domain = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN'); + $isEuRegion = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION'); if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { $this->markTestSkipped('Email provider not configured'); } @@ -415,7 +417,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'userId' => ID::custom('test-user'), - 'email' => 'prateekbanga12@gmail.com', + 'email' => $to, 'password' => 'password', 'name' => 'Messaging User', ], false); @@ -454,7 +456,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'providerId' => $provider['body']['$id'], - 'to' => [$target['body']['$id']], + 'to' => [$topic['body']['$id']], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ]); From ffeb3f8fcf1a8f954ad4ced75e057ffa1489ab72 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Tue, 17 Oct 2023 22:53:26 +0530 Subject: [PATCH 14/18] review work --- app/controllers/api/messaging.php | 45 ++-- app/init.php | 1 + app/workers/deletes.php | 22 ++ .../Database/Validator/Queries/Messages.php | 3 +- .../Database/Validator/Queries/Providers.php | 2 - tests/e2e/Services/GraphQL/MessagingTest.php | 198 +++++++++--------- 6 files changed, 146 insertions(+), 125 deletions(-) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index b3deaa409f..9a2f5b9856 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -1,5 +1,6 @@ getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('$id', [$providerId]), - ])); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); - if ($cursorDocument === false || $cursorDocument->isEmpty()) { + if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found."); } @@ -1242,8 +1241,8 @@ App::post('/v1/messaging/topics') } $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($topic, Response::MODEL_TOPIC); + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($topic, Response::MODEL_TOPIC); }); App::get('/v1/messaging/topics') @@ -1269,12 +1268,9 @@ App::get('/v1/messaging/topics') if ($cursor) { $topicId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->find('topics', [ - Query::equal('$id', [$topicId]), - Query::limit(1), - ])); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); - if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) { + if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found."); } @@ -1330,7 +1326,7 @@ App::patch('/v1/messaging/topics/:topicId') ->label('sdk.response.model', Response::MODEL_TOPIC) ->param('topicId', '', new UID(), 'Topic ID.') ->param('name', '', new Text(128), 'Topic Name.', true) - ->param('description', null, new Text(2048), 'Topic Description.', true) + ->param('description', '', new Text(2048), 'Topic Description.', true) ->inject('dbForProject') ->inject('response') ->action(function (string $topicId, string $name, string $description, Database $dbForProject, Response $response) { @@ -1340,11 +1336,11 @@ App::patch('/v1/messaging/topics/:topicId') throw new Exception(Exception::TOPIC_NOT_FOUND); } - if ($name) { + if (!empty($name)) { $topic->setAttribute('name', $name); } - if ($description) { + if (!empty($description)) { $topic->setAttribute('description', $description); } @@ -1369,15 +1365,20 @@ App::delete('/v1/messaging/topics/:topicId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('topicId', '', new UID(), 'Topic ID.') ->inject('dbForProject') + ->inject('deletes') ->inject('response') - ->action(function (string $topicId, Database $dbForProject, Response $response) { + ->action(function (string $topicId, Database $dbForProject, Delete $deletes, Response $response) { $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - $topic = $dbForProject->deleteDocument('topics', $topicId); + $dbForProject->deleteDocument('topics', $topicId); + + $deletes + ->setType(DELETE_TYPE_SUBSCRIBERS) + ->setDocument($topic); $response ->setStatusCode(Response::STATUS_CODE_NOCONTENT) @@ -1437,8 +1438,8 @@ App::post('/v1/messaging/topics/:topicId/subscribers') } $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); App::get('/v1/messaging/topics/:topicId/subscribers') @@ -1563,7 +1564,7 @@ App::post('/v1/messaging/messages/email') ->param('content', '', new Text(64230), 'Email Content.') ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) - ->param('deliveryTime', null, new DatetimeValidator(), 'Delivery time for message.', true) + ->param('deliveryTime', null, new DatetimeValidator(false), 'Delivery time for message.', true) ->inject('dbForProject') ->inject('project') ->inject('messaging') @@ -1634,11 +1635,9 @@ App::get('/v1/messaging/messages') if ($cursor) { $messageId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->findOne('messages', [ - Query::equal('$id', [$messageId]), - ])); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId)); - if ($cursorDocument === false || $cursorDocument->isEmpty()) { + if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found."); } diff --git a/app/init.php b/app/init.php index 5b40ad386c..cc1c77c6ea 100644 --- a/app/init.php +++ b/app/init.php @@ -169,6 +169,7 @@ const DELETE_TYPE_SESSIONS = 'sessions'; const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp'; const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource'; const DELETE_TYPE_SCHEDULES = 'schedules'; +const DELETE_TYPE_SUBSCRIBERS = 'subscribers'; // Compression type const COMPRESSION_TYPE_NONE = 'none'; const COMPRESSION_TYPE_GZIP = 'gzip'; diff --git a/app/workers/deletes.php b/app/workers/deletes.php index f831c1df3f..d5e319a662 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -127,6 +127,10 @@ class DeletesV1 extends Worker case DELETE_TYPE_SCHEDULES: $this->deleteSchedules($this->args['datetime']); break; + case DELETE_TYPE_SUBSCRIBERS: + $topic = new Document($this->args['document'] ?? []); + $this->deleteSubscribers($project, $topic); + break; default: Console::error('No delete operation for type: ' . $type); break; @@ -170,6 +174,24 @@ class DeletesV1 extends Worker ); } + /** + * @param Document $project + * @param Document $topic + * @throws Exception + */ + protected function deleteSubscribers(Document $project, Document $topic) + { + if ($topic->isEmpty()) { + Console::error('Failed to delete subscribers. Topic not found'); + return; + } + $dbForProject = $this->getProjectDB($project); + + $this->deleteByGroup('subscribers', [ + Query::equal('topicInternalId', [$topic->getInternalId()]) + ], $dbForProject); + } + /** * @param Document $project * @param string $resource diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php index 5fa391e7f6..4bff13ae19 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php @@ -11,7 +11,8 @@ class Messages extends Base 'deliveredTo', 'deliveryErrors', 'status', - 'description' + 'description', + 'data' ]; /** diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php index ad0e08403c..1fd6c9e9f8 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php @@ -10,8 +10,6 @@ class Providers extends Base 'type', 'default', 'enabled', - 'credentials', - 'options' ]; /** diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 178b9a6a6c..8c3e4970e7 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -270,8 +270,8 @@ class MessagingTest extends Scope ]; $query = $this->getQuery(self::$CREATE_SENDGRID_PROVIDER); $graphQLPayload = [ - 'query' => $query, - 'variables' => $providerParam['sendgrid'], + 'query' => $query, + 'variables' => $providerParam['sendgrid'], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -283,13 +283,13 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_TOPIC); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => $providerId, - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Active users', - ], + 'query' => $query, + 'variables' => [ + 'providerId' => $providerId, + 'topicId' => ID::unique(), + 'name' => 'topic1', + 'description' => 'Active users', + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -312,12 +312,12 @@ class MessagingTest extends Scope $topicId = $topic['_id']; $query = $this->getQuery(self::$UPDATE_TOPIC); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => $topicId, - 'name' => 'topic2', - 'description' => 'Inactive users', - ], + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + 'name' => 'topic2', + 'description' => 'Inactive users', + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -339,7 +339,7 @@ class MessagingTest extends Scope { $query = $this->getQuery(self::$LIST_TOPICS); $graphQLPayload = [ - 'query' => $query, + 'query' => $query, ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -358,10 +358,10 @@ class MessagingTest extends Scope { $query = $this->getQuery(self::$GET_TOPIC); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => $topicId, - ], + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -385,13 +385,13 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_USER_TARGET); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'targetId' => ID::unique(), - 'userId' => $userId, - 'providerId' => $topic['providerId'], - 'identifier' => 'token', - ], + 'query' => $query, + 'variables' => [ + 'targetId' => ID::unique(), + 'userId' => $userId, + 'providerId' => $topic['providerId'], + 'identifier' => 'token', + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -407,12 +407,12 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_SUBSCRIBER); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'subscriberId' => ID::unique(), - 'topicId' => $topicId, - 'targetId' => $targetId, - ], + 'query' => $query, + 'variables' => [ + 'subscriberId' => ID::unique(), + 'topicId' => $topicId, + 'targetId' => $targetId, + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -431,10 +431,10 @@ class MessagingTest extends Scope { $query = $this->getQuery(self::$LIST_SUBSCRIBERS); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => $topicId, - ], + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -456,11 +456,11 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$GET_SUBSCRIBER); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => $topicId, - 'subscriberId' => $subscriberId, - ], + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + 'subscriberId' => $subscriberId, + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -483,11 +483,11 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$DELETE_SUBSCRIBER); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => $topicId, - 'subscriberId' => $subscriberId, - ], + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + 'subscriberId' => $subscriberId, + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -504,10 +504,10 @@ class MessagingTest extends Scope { $query = $this->getQuery(self::$DELETE_TOPIC); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'topicId' => $topicId, - ], + 'query' => $query, + 'variables' => [ + 'topicId' => $topicId, + ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -531,15 +531,15 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_MAILGUN_PROVIDER); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => ID::unique(), - 'name' => 'Mailgun1', - 'apiKey' => $apiKey, - 'domain' => $domain, - 'from' => $from, - 'isEuRegion' => $isEuRegion, - ], + 'query' => $query, + 'variables' => [ + 'providerId' => ID::unique(), + 'name' => 'Mailgun1', + 'apiKey' => $apiKey, + 'domain' => $domain, + 'from' => $from, + 'isEuRegion' => $isEuRegion, + ], ]; $provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -553,13 +553,13 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_TOPIC); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => $providerId, - 'topicId' => ID::unique(), - 'name' => 'topic1', - 'description' => 'Active users', - ], + 'query' => $query, + 'variables' => [ + 'providerId' => $providerId, + 'topicId' => ID::unique(), + 'name' => 'topic1', + 'description' => 'Active users', + ], ]; $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -571,13 +571,13 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_USER); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'userId' => ID::custom('test-user'), - 'email' => $to, - 'password' => 'password', - 'name' => 'Messaging User', - ] + 'query' => $query, + 'variables' => [ + 'userId' => ID::custom('test-user'), + 'email' => $to, + 'password' => 'password', + 'name' => 'Messaging User', + ] ]; $user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -589,13 +589,13 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_USER_TARGET); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'targetId' => ID::unique(), - 'userId' => $user['body']['data']['usersCreate']['_id'], - 'providerId' => $providerId, - 'identifier' => $to, - ], + 'query' => $query, + 'variables' => [ + 'targetId' => ID::unique(), + 'userId' => $user['body']['data']['usersCreate']['_id'], + 'providerId' => $providerId, + 'identifier' => $to, + ], ]; $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -607,12 +607,12 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_SUBSCRIBER); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'subscriberId' => ID::unique(), - 'topicId' => $topic['body']['data']['messagingCreateTopic']['_id'], - 'targetId' => $target['body']['data']['usersCreateTarget']['_id'], - ], + 'query' => $query, + 'variables' => [ + 'subscriberId' => ID::unique(), + 'topicId' => $topic['body']['data']['messagingCreateTopic']['_id'], + 'targetId' => $target['body']['data']['usersCreateTarget']['_id'], + ], ]; $subscriber = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -623,14 +623,14 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$CREATE_EMAIL); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'messageId' => ID::unique(), - 'providerId' => $providerId, - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], - 'subject' => 'Khali beats Undertaker', - 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', - ], + 'query' => $query, + 'variables' => [ + 'messageId' => ID::unique(), + 'providerId' => $providerId, + 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'subject' => 'Khali beats Undertaker', + 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + ], ]; $email = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', @@ -644,10 +644,10 @@ class MessagingTest extends Scope $query = $this->getQuery(self::$GET_MESSAGE); $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'messageId' => $email['body']['data']['messagingCreateEmail']['_id'], - ], + 'query' => $query, + 'variables' => [ + 'messageId' => $email['body']['data']['messagingCreateEmail']['_id'], + ], ]; $message = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ 'content-type' => 'application/json', From 8602c44d14ad5af3be4e2ba6ed63259866322f25 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Tue, 17 Oct 2023 23:29:10 +0530 Subject: [PATCH 15/18] adds user targets graphql testsl --- tests/e2e/Services/GraphQL/UsersTest.php | 145 +++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/tests/e2e/Services/GraphQL/UsersTest.php b/tests/e2e/Services/GraphQL/UsersTest.php index 9bd503df0f..c0327381cd 100644 --- a/tests/e2e/Services/GraphQL/UsersTest.php +++ b/tests/e2e/Services/GraphQL/UsersTest.php @@ -45,6 +45,55 @@ class UsersTest extends Scope return $user; } + /** + * @depends testCreateUser + */ + public function testCreateUserTarget(array $user) + { + $projectId = $this->getProject()['$id']; + + $query = $this->getQuery(self::$CREATE_MAILGUN_PROVIDER); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'providerId' => ID::unique(), + 'name' => 'Mailgun1', + 'apiKey' => 'api-key', + 'domain' => 'domain', + 'from' => 'from@domain', + 'isEuRegion' => false, + ], + ]; + $provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $graphQLPayload); + $providerId = $provider['body']['data']['messagingCreateMailgunProvider']['_id']; + + $this->assertEquals(200, $provider['headers']['status-code']); + + $query = $this->getQuery(self::$CREATE_USER_TARGET); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'targetId' => ID::unique(), + 'userId' => $user['_id'], + 'providerId' => $providerId, + 'identifier' => 'identifier', + ] + ]; + + $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $target['headers']['status-code']); + $this->assertEquals('identifier', $target['body']['data']['usersCreateTarget']['identifier']); + + return $target['body']['data']['usersCreateTarget']; + } + public function testGetUsers() { $projectId = $this->getProject()['$id']; @@ -176,6 +225,54 @@ class UsersTest extends Scope $this->assertIsArray($user['body']['data']['usersListLogs']); } + /** + * @depends testCreateUserTarget + */ + public function testListUserTargets(array $target) + { + $projectId = $this->getProject()['$id']; + $query = $this->getQuery(self::$LIST_USER_TARGETS); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'userId' => $target['userId'], + ] + ]; + + $targets = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $targets['headers']['status-code']); + $this->assertIsArray($targets['body']['data']['usersListTargets']); + $this->assertCount(1, $targets['body']['data']['usersListTargets']['targets']); + } + + /** + * @depends testCreateUserTarget + */ + public function testGetUserTarget(array $target) + { + $projectId = $this->getProject()['$id']; + $query = $this->getQuery(self::$GET_USER_TARGET); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'userId' => $target['userId'], + 'targetId' => $target['_id'], + ] + ]; + + $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $target['headers']['status-code']); + $this->assertEquals('identifier', $target['body']['data']['usersGetTarget']['identifier']); + } + public function testUpdateUserStatus() { $projectId = $this->getProject()['$id']; @@ -360,6 +457,31 @@ class UsersTest extends Scope $this->assertEquals('{"key":"value"}', $user['body']['data']['usersUpdatePrefs']['data']); } + /** + * @depends testCreateUserTarget + */ + public function testUpdateUserTarget(array $target) + { + $projectId = $this->getProject()['$id']; + $query = $this->getQuery(self::$UPDATE_USER_TARGET); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'userId' => $target['userId'], + 'targetId' => $target['_id'], + 'identifier' => 'newidentifier', + ], + ]; + + $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(200, $target['headers']['status-code']); + $this->assertEquals('newidentifier', $target['body']['data']['usersUpdateTargetIdentifier']['identifier']); + } + public function testDeleteUserSessions() { $projectId = $this->getProject()['$id']; @@ -407,6 +529,29 @@ class UsersTest extends Scope $this->getUser(); } + /** + * @depends testCreateUserTarget + */ + public function testDeleteUserTarget(array $target) + { + $projectId = $this->getProject()['$id']; + $query = $this->getQuery(self::$DELETE_USER_TARGET); + $graphQLPayload = [ + 'query' => $query, + 'variables' => [ + 'userId' => $target['userId'], + 'targetId' => $target['_id'], + ] + ]; + + $target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $graphQLPayload); + + $this->assertEquals(204, $target['headers']['status-code']); + } + public function testDeleteUser() { $projectId = $this->getProject()['$id']; From b86f531b309ff44071d8e91e618b49674df5d8dc Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Tue, 17 Oct 2023 23:45:46 +0530 Subject: [PATCH 16/18] adds data in message model and search param in messages query validator --- .../Utopia/Database/Validator/Queries/Messages.php | 3 ++- src/Appwrite/Utopia/Response/Model/Message.php | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php index 4bff13ae19..d21aa5de1b 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php @@ -12,7 +12,8 @@ class Messages extends Base 'deliveryErrors', 'status', 'description', - 'data' + 'data', + 'search' ]; /** diff --git a/src/Appwrite/Utopia/Response/Model/Message.php b/src/Appwrite/Utopia/Response/Model/Message.php index 5e4a358490..0cadc378d9 100644 --- a/src/Appwrite/Utopia/Response/Model/Message.php +++ b/src/Appwrite/Utopia/Response/Model/Message.php @@ -58,6 +58,15 @@ class Message extends Any 'default' => 0, 'example' => 1, ]) + ->addRule('data', [ + 'type' => self::TYPE_JSON, + 'description' => 'Data of the message.', + 'default' => [], + 'example' => [ + 'subject' => 'Welcome to Appwrite', + 'content' => 'Hi there, welcome to Appwrite family.', + ], + ]) ->addRule('status', [ 'type' => self::TYPE_STRING, 'description' => 'Status of delivery.', From 4bef86d88b15f41589b736369026774222d29cd0 Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Wed, 18 Oct 2023 13:19:21 +0530 Subject: [PATCH 17/18] review changes --- app/controllers/api/databases.php | 7 +++- app/controllers/api/messaging.php | 39 ++++++++++++----- composer.json | 6 +-- composer.lock | 42 +++++++++---------- .../Validator/Queries/Subscribers.php | 20 +++++++++ 5 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 0c60c01a1e..9eb243521b 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -2489,8 +2489,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') 'orders' => $orders, ]); - $validator = new IndexValidator($dbForProject->getAdapter()->getMaxIndexLength()); - if (!$validator->isValid($collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND))) { + $validator = new IndexValidator( + $collection->getAttribute('attributes'), + $dbForProject->getAdapter()->getMaxIndexLength() + ); + if (!$validator->isValid($index)) { throw new Exception(Exception::INDEX_INVALID, $validator->getDescription()); } diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 9a2f5b9856..72f0b9fdb4 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -8,6 +8,7 @@ use Appwrite\Role; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Messages; use Appwrite\Utopia\Database\Validator\Queries\Providers; +use Appwrite\Utopia\Database\Validator\Queries\Subscribers; use Appwrite\Utopia\Database\Validator\Queries\Topics; use Appwrite\Utopia\Response; use Utopia\App; @@ -1454,23 +1455,41 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SUBSCRIBER_LIST) ->param('topicId', '', new UID(), 'Topic ID.') + ->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) ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, Database $dbForProject, Response $response) { + ->action(function (string $topicId, array $queries, Database $dbForProject, Response $response) { + $queries = Query::parseQueries($queries); + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - $subscribers = $dbForProject->find('subscribers', [ - Query::equal('topicInternalId', [$topic->getInternalId()]) - ]); + \array_push($queries, Query::equal('topicInternalId', [$topic->getInternalId()])); + + // Get cursor document if there was a cursor query + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = reset($cursor); + + if ($cursor) { + $subscriberId = $cursor->getValue(); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); + + if ($cursorDocument->isEmpty()) { + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found."); + } + + $cursor->setValue($cursorDocument); + } + + $filterQueries = Query::groupByType($queries)['filters']; $response ->dynamic(new Document([ - 'subscribers' => $subscribers, - 'total' => \count($subscribers), + 'subscribers' => $dbForProject->find('subscribers', $queries), + 'total' => $dbForProject->count('subscribers', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_SUBSCRIBER_LIST); }); @@ -1562,9 +1581,9 @@ App::post('/v1/messaging/messages/email') ->param('subject', '', new Text(998), 'Email Subject.') ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.') - ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status.', 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(false), 'Delivery time for message.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) ->inject('dbForProject') ->inject('project') ->inject('messaging') @@ -1693,9 +1712,9 @@ 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.', 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(), 'Delivery time for message in ISO 8601 format.', true) + ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) ->inject('dbForProject') ->inject('project') ->inject('messaging') diff --git a/composer.json b/composer.json index ed5cfbd825..f4d92146c0 100644 --- a/composer.json +++ b/composer.json @@ -43,13 +43,13 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.13.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "0.31.*", + "utopia-php/abuse": "0.32.*", "utopia-php/analytics": "0.10.*", - "utopia-php/audit": "0.33.*", + "utopia-php/audit": "0.34.*", "utopia-php/cache": "0.8.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.43.*", + "utopia-php/database": "0.44.*", "utopia-php/domains": "0.3.*", "utopia-php/dsn": "0.1.*", "utopia-php/framework": "0.31.0", diff --git a/composer.lock b/composer.lock index f80104986c..8f7ca86d06 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "34cb0b1c81424d1858df197aed030793", + "content-hash": "ee4518740e581a9a4889936fb584a5a4", "packages": [ { "name": "adhocore/jwt", @@ -1861,23 +1861,23 @@ }, { "name": "utopia-php/abuse", - "version": "0.31.1", + "version": "0.32.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "b2ad372d1070f55f9545cb811b6ed2d40094e6dd" + "reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/b2ad372d1070f55f9545cb811b6ed2d40094e6dd", - "reference": "b2ad372d1070f55f9545cb811b6ed2d40094e6dd", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/9717ffb2d7711f3fd621bb6df3edf5724c08ea78", + "reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78", "shasum": "" }, "require": { "ext-curl": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": "0.43.*" + "utopia-php/database": "0.44.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1904,9 +1904,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.31.1" + "source": "https://github.com/utopia-php/abuse/tree/0.32.0" }, - "time": "2023-08-29T11:07:46+00:00" + "time": "2023-10-18T07:28:55+00:00" }, { "name": "utopia-php/analytics", @@ -1956,21 +1956,21 @@ }, { "name": "utopia-php/audit", - "version": "0.33.1", + "version": "0.34.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "c117e8e9ce4e3e1b369e8b5b55b2d6ab3138eadd" + "reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/c117e8e9ce4e3e1b369e8b5b55b2d6ab3138eadd", - "reference": "c117e8e9ce4e3e1b369e8b5b55b2d6ab3138eadd", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/cf34cc3f9f20da4e574a9be4517e1a11025a858f", + "reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.43.*" + "utopia-php/database": "0.44.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1997,9 +1997,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.33.1" + "source": "https://github.com/utopia-php/audit/tree/0.34.0" }, - "time": "2023-08-29T11:07:40+00:00" + "time": "2023-10-18T07:43:25+00:00" }, { "name": "utopia-php/cache", @@ -2152,16 +2152,16 @@ }, { "name": "utopia-php/database", - "version": "0.43.5", + "version": "0.44.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "5f7b05189cfbcc0506090498c580c5765375a00a" + "reference": "e0b832d217e4d429c96ade671e85ece942446543" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/5f7b05189cfbcc0506090498c580c5765375a00a", - "reference": "5f7b05189cfbcc0506090498c580c5765375a00a", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e0b832d217e4d429c96ade671e85ece942446543", + "reference": "e0b832d217e4d429c96ade671e85ece942446543", "shasum": "" }, "require": { @@ -2202,9 +2202,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.43.5" + "source": "https://github.com/utopia-php/database/tree/0.44.1" }, - "time": "2023-10-06T06:49:47+00:00" + "time": "2023-10-18T07:05:41+00:00" }, { "name": "utopia-php/domains", diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php new file mode 100644 index 0000000000..048ae60033 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php @@ -0,0 +1,20 @@ + Date: Wed, 18 Oct 2023 13:30:01 +0530 Subject: [PATCH 18/18] fix typo --- src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php index 048ae60033..55bb455903 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Subscribers.php @@ -15,6 +15,6 @@ class Subscribers extends Base */ public function __construct() { - parent::__construct('messages', self::ALLOWED_ATTRIBUTES); + parent::__construct('subscribers', self::ALLOWED_ATTRIBUTES); } }