diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0522e33da5..529f6103d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -344,13 +344,13 @@ Things to remember when releasing SDKs: ## Debug -Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code's [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension. +Appwrite uses [XDebug](https://github.com/xdebug/xdebug) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code's [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension. If you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection: -1. Create an init file. -2. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php**. -3. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file. +1. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file. +2. If needed edit the **dev/xdebug.ini** file to your needs. +3. Launch your Appwrite instance while your debugger is listening for connections. ### VS Code Launch Configuration diff --git a/Dockerfile b/Dockerfile index 7c37d4a4f1..968b0bb22a 100755 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT RUN npm ci RUN npm run build -FROM appwrite/base:0.4.3 as final +FROM appwrite/base:0.7.2 as final LABEL maintainer="team@appwrite.io" @@ -56,6 +56,7 @@ COPY ./public /usr/src/code/public COPY ./bin /usr/local/bin COPY ./docs /usr/src/code/docs COPY ./src /usr/src/code/src +COPY ./dev /usr/src/code/dev # Set Volumes RUN mkdir -p /storage/uploads && \ @@ -126,9 +127,10 @@ RUN chmod +x /usr/local/bin/calc-tier-stats && \ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ # Enable Extensions -RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/src/code/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi - +RUN if [ "$DEBUG" == "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi RUN if [ "$DEBUG" == "true" ]; then echo "opcache.enable=0" >> /usr/local/etc/php/conf.d/appwrite.ini; fi +RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi +RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so; fi RUN echo "opcache.preload_user=www-data" >> /usr/local/etc/php/conf.d/appwrite.ini RUN echo "opcache.preload=/usr/src/code/app/preload.php" >> /usr/local/etc/php/conf.d/appwrite.ini RUN echo "opcache.enable_cli=1" >> /usr/local/etc/php/conf.d/appwrite.ini diff --git a/app/cli.php b/app/cli.php index d7cc5fe414..668b70d8b7 100644 --- a/app/cli.php +++ b/app/cli.php @@ -72,7 +72,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) { $collections = Config::getParam('collections', [])['console']; $last = \array_key_last($collections); - if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */ + if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */ throw new Exception('Tables not ready yet.'); } diff --git a/app/config/collections.php b/app/config/collections.php index 606cae782a..3fb44cdea9 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1731,17 +1731,6 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], - [ - '$id' => ID::custom('description'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => '', - 'array' => false, - 'filters' => [], - ], [ '$id' => ID::custom('status'), 'type' => Database::VAR_STRING, @@ -1902,17 +1891,6 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], - [ - '$id' => ID::custom('description'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 2048, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], [ '$id' => ID::custom('total'), 'type' => Database::VAR_INTEGER, @@ -2194,6 +2172,17 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('expired'), + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => false, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ diff --git a/app/config/errors.php b/app/config/errors.php index d35dc9c622..1026abb47c 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -816,6 +816,11 @@ return [ 'description' => 'Provider with the requested ID is of the incorrect type.', 'code' => 400, ], + Exception::PROVIDER_MISSING_CREDENTIALS => [ + 'name' => Exception::PROVIDER_MISSING_CREDENTIALS, + 'description' => 'Provider with the requested ID is missing credentials.', + 'code' => 400, + ], /** Topics */ Exception::TOPIC_NOT_FOUND => [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4244c6d2f8..accf9f4ae6 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -174,7 +174,8 @@ App::post('/v1/account') ]); $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -279,7 +280,7 @@ App::post('/v1/account/sessions/email') $dbForProject->updateDocument('users', $user->getId(), $user); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [ Permission::read(Role::user($user->getId())), @@ -613,7 +614,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $currentDocument = $dbForProject->getDocument('sessions', $current); if (!$currentDocument->isEmpty()) { $dbForProject->deleteDocument('sessions', $currentDocument->getId()); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } } @@ -876,7 +877,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $state['success']['query'] = URLParser::unparseQuery($query); $state['success'] = URLParser::unparse($state['success']); @@ -915,7 +916,7 @@ App::get('/v1/account/identities') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -1098,7 +1099,7 @@ App::post('/v1/account/tokens/magic-url') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); if (empty($url)) { $url = $request->getProtocol() . '://' . $request->getHostname() . '/auth/magic-url'; @@ -1336,7 +1337,7 @@ App::post('/v1/account/tokens/email') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $subject = $locale->getText("emails.otpSession.subject"); $customTemplate = $project->getAttribute('templates', [])['email.otpSession-' . $locale->default] ?? []; @@ -1509,9 +1510,9 @@ $createSession = function (string $userId, string $secret, Request $request, Res Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); if ($verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_MAGIC_URL) { $user->setAttribute('emailVerification', true); @@ -1708,7 +1709,7 @@ App::post('/v1/account/tokens/phone') ]); $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } $secret = Auth::codeGenerator(); @@ -1734,7 +1735,7 @@ App::post('/v1/account/tokens/phone') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl'); @@ -1894,7 +1895,7 @@ App::post('/v1/account/sessions/anonymous') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -2030,7 +2031,7 @@ App::post('/v1/account/targets/push') } catch (Duplicate) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -2408,7 +2409,7 @@ App::patch('/v1/account/email') if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -2489,7 +2490,7 @@ App::patch('/v1/account/phone') if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS); } @@ -2635,7 +2636,7 @@ App::delete('/v1/account/sessions/:sessionId') ; } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -2714,7 +2715,7 @@ App::patch('/v1/account/sessions/:sessionId') // Save changes $dbForProject->updateDocument('sessions', $sessionId, $session); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -2775,7 +2776,7 @@ App::delete('/v1/account/sessions') } } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -2860,7 +2861,7 @@ App::post('/v1/account/recovery') Permission::delete(Role::user($profile->getId())), ])); - $dbForProject->deleteCachedDocument('users', $profile->getId()); + $dbForProject->purgeCachedDocument('users', $profile->getId()); $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]); @@ -3036,7 +3037,7 @@ App::put('/v1/account/recovery') * the recovery token but actually we don't need it anymore. */ $dbForProject->deleteDocument('tokens', $verifiedToken->getId()); - $dbForProject->deleteCachedDocument('users', $profile->getId()); + $dbForProject->purgeCachedDocument('users', $profile->getId()); $queueForEvents ->setParam('userId', $profile->getId()) @@ -3107,7 +3108,7 @@ App::post('/v1/account/verification') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]); @@ -3256,7 +3257,7 @@ App::put('/v1/account/verification') * the verification token but actually we don't need it anymore. */ $dbForProject->deleteDocument('tokens', $verifiedToken->getId()); - $dbForProject->deleteCachedDocument('users', $profile->getId()); + $dbForProject->purgeCachedDocument('users', $profile->getId()); $queueForEvents ->setParam('userId', $userId) @@ -3329,7 +3330,7 @@ App::post('/v1/account/verification/phone') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl'); @@ -3425,7 +3426,7 @@ App::put('/v1/account/verification/phone') * We act like we're updating and validating the verification token but actually we don't need it anymore. */ $dbForProject->deleteDocument('tokens', $verifiedToken->getId()); - $dbForProject->deleteCachedDocument('users', $profile->getId()); + $dbForProject->purgeCachedDocument('users', $profile->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -3865,7 +3866,7 @@ App::put('/v1/account/targets/:targetId/push') $target->setAttribute('name', "{$device['deviceBrand']} {$device['deviceModel']}"); $target = $dbForProject->updateDocument('targets', $target->getId(), $target); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index b6395774e9..1c5d1ae34a 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -116,7 +116,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Throwable $err) { $index = 0; do { @@ -521,7 +521,7 @@ App::get('/v1/avatars/initials') // if there is no space, try to split by `_` underscore $words = (count($words) == 1) ? \explode('_', \strtoupper($name)) : $words; - $initials = null; + $initials = ''; $code = 0; foreach ($words as $key => $w) { diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index ccfc93f137..9312831fe0 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -28,7 +28,6 @@ use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; -use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -153,13 +152,13 @@ function createAttribute(string $databaseId, string $collectionId, Document $att } catch (LimitException) { throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded'); } catch (\Exception $e) { - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId); - $dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId); + $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); throw $e; } - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId); - $dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId); + $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) { $twoWayKey = $options['twoWayKey']; @@ -197,13 +196,13 @@ function createAttribute(string $databaseId, string $collectionId, Document $att $dbForProject->deleteDocument('attributes', $attribute->getId()); throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded'); } catch (\Exception $e) { - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); - $dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); + $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); throw $e; } - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); - $dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); + $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); } $queueForDatabase @@ -358,7 +357,7 @@ function updateAttribute( $relatedOptions = \array_merge($relatedAttribute->getAttribute('options'), $options); $relatedAttribute->setAttribute('options', $relatedOptions); $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey'], $relatedAttribute); - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); } } else { $dbForProject->updateAttribute( @@ -371,7 +370,7 @@ function updateAttribute( } $attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute); - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collection->getId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collection->getId()); $queueForEvents ->setContext('collection', $collection) @@ -496,7 +495,7 @@ App::get('/v1/databases') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -703,8 +702,8 @@ App::delete('/v1/databases/:databaseId') throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB'); } - $dbForProject->deleteCachedDocument('databases', $database->getId()); - $dbForProject->deleteCachedCollection('databases_' . $database->getInternalId()); + $dbForProject->purgeCachedDocument('databases', $database->getId()); + $dbForProject->purgeCachedCollection('databases_' . $database->getInternalId()); $queueForDatabase ->setType(DATABASE_TYPE_DELETE_DATABASE) @@ -818,7 +817,7 @@ App::get('/v1/databases/:databaseId/collections') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -901,6 +900,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); } + $collectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); $collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collectionDocument->getInternalId()); @@ -1076,7 +1076,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB'); } - $dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); + $dbForProject->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); $queueForDatabase ->setType(DATABASE_TYPE_DELETE_COLLECTION) @@ -1672,7 +1672,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = \reset($cursor); @@ -2248,8 +2248,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key $attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting')); } - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId); - $dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId); + $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { $options = $attribute->getAttribute('options'); @@ -2270,8 +2270,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key $dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'deleting')); } - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $options['relatedCollection']); - $dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $options['relatedCollection']); + $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); } } @@ -2330,7 +2330,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', null, new Key(), 'Index Key.') - ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') + ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.') ->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.') ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true) ->inject('response') @@ -2453,7 +2453,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') throw new Exception(Exception::INDEX_ALREADY_EXISTS); } - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId); $queueForDatabase ->setType(DATABASE_TYPE_CREATE_INDEX) @@ -2509,7 +2509,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); @@ -2620,7 +2620,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') $index = $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting')); } - $dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId); + $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId); $queueForDatabase ->setType(DATABASE_TYPE_DELETE_INDEX) @@ -2893,13 +2893,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents') ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true) + ->param('queries', [], new JSON(), '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.', true) ->inject('response') ->inject('dbForProject') ->inject('mode') ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { + $queries = Query::parseQueries($queries); $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -2913,11 +2913,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - $queries = Query::parseQueries($queries); - // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = \reset($cursor); @@ -2934,11 +2932,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $cursor->setValue($cursorDocument); } - $filters = Query::groupByType($queries)['filters']; try { $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); - $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filters, APP_LIMIT_COUNT); + $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); } catch (AuthorizationException) { throw new Exception(Exception::USER_UNAUTHORIZED); } catch (QueryException $e) { @@ -3020,7 +3017,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('documentId', '', new UID(), 'Document ID.') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only method allowed is select.', true) + ->param('queries', [], new JSON(), '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.', true) ->inject('response') ->inject('dbForProject') ->inject('mode') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 386013c81a..9c1f3cfa98 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -374,7 +374,7 @@ App::get('/v1/functions') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -1268,7 +1268,7 @@ App::get('/v1/functions/:functionId/deployments') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -1805,7 +1805,7 @@ App::get('/v1/functions/:functionId/executions') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index ef72be19cf..258c193a52 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -35,6 +35,7 @@ use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; use Utopia\Validator\Integer; use Utopia\Validator\JSON; +use Utopia\Validator\Range; use Utopia\Validator\Text; use MaxMind\Db\Reader; use Utopia\Database\DateTime; @@ -61,30 +62,20 @@ App::post('/v1/messaging/providers/mailgun') ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) ->param('domain', '', new Text(0), 'Mailgun Domain.', true) ->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true) - ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('fromName', '', new Text(128), 'Sender Name.', true) + ->param('fromName', '', new Text(128, 0), 'Sender Name.', true) ->param('fromEmail', '', new Email(), 'Sender email address.', true) - ->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name. Reply to name must have reply to email as well.', true) - ->param('replyToEmail', '', new Text(128), 'Email set in the reply to field for the mail. Default value is sender email. Reply to email must have reply to name as well.', true) + ->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name. Reply to name must have reply to email as well.', true) + ->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email. Reply to email must have reply to name as well.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $apiKey, string $domain, ?bool $isEuRegion, ?bool $enabled, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $apiKey, string $domain, ?bool $isEuRegion, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $options = [ - 'fromName' => $fromName, - 'fromEmail' => $fromEmail, - ]; - - if (!empty($replyToName) && !empty($replyToEmail)) { - $options['replyToName'] = $replyToName; - $options['replyToEmail'] = $replyToEmail; - } - $credentials = []; - if ($isEuRegion === true || $isEuRegion === false) { + if (!\is_null($isEuRegion)) { $credentials['isEuRegion'] = $isEuRegion; } @@ -96,12 +87,19 @@ App::post('/v1/messaging/providers/mailgun') $credentials['domain'] = $domain; } + $options = [ + 'fromName' => $fromName, + 'fromEmail' => $fromEmail, + 'replyToName' => $replyToName, + 'replyToEmail' => $replyToEmail, + ]; + if ( - $enabled === true && - \array_key_exists('isEuRegion', $credentials) && - \array_key_exists('apiKey', $credentials) && - \array_key_exists('domain', $credentials) && - \array_key_exists('from', $options) + $enabled === true + && !empty($fromEmail) + && \array_key_exists('isEuRegion', $credentials) + && \array_key_exists('apiKey', $credentials) + && \array_key_exists('domain', $credentials) ) { $enabled = true; } else { @@ -149,37 +147,34 @@ App::post('/v1/messaging/providers/sendgrid') ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Provider name.') ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) - ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('fromName', '', new Text(128), 'Sender Name.', true) + ->param('fromName', '', new Text(128, 0), 'Sender Name.', true) ->param('fromEmail', '', new Email(), 'Sender email address.', true) - ->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name.', true) - ->param('replyToEmail', '', new Text(128), 'Email set in the reply to field for the mail. Default value is sender email.', true) + ->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true) + ->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $apiKey, ?bool $enabled, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $apiKey, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; - $options = [ - 'fromName' => $fromName, - 'fromEmail' => $fromEmail, - ]; - - if (!empty($replyToName) && !empty($replyToEmail)) { - $options['replyToName'] = $replyToName; - $options['replyToEmail'] = $replyToEmail; - } - $credentials = []; if (!empty($apiKey)) { $credentials['apiKey'] = $apiKey; } + $options = [ + 'fromName' => $fromName, + 'fromEmail' => $fromEmail, + 'replyToName' => $replyToName, + 'replyToEmail' => $replyToEmail, + ]; + if ( $enabled === true + && !empty($fromEmail) && \array_key_exists('apiKey', $credentials) - && \array_key_exists('from', $options) ) { $enabled = true; } else { @@ -210,6 +205,94 @@ App::post('/v1/messaging/providers/sendgrid') ->dynamic($provider, Response::MODEL_PROVIDER); }); +App::post('/v1/messaging/providers/smtp') + ->desc('Create SMTP provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'provider.create') + ->label('audits.resource', 'provider/{response.$id}') + ->label('event', 'providers.[providerId].create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createSMTPProvider') + ->label('sdk.description', '/docs/references/messaging/create-smtp-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') + ->param('name', '', new Text(128), 'Provider name.') + ->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host by using this format: [hostname:port] (e.g. "smtp1.example.com:25;smtp2.example.com"). You can also specify encryption type, for example: (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). Hosts will be tried in order.') + ->param('port', 587, new Range(1, 65535), 'The default SMTP server port.', true) + ->param('username', '', new Text(0), 'Authentication username.', true) + ->param('password', '', new Text(0), 'Authentication password.', true) + ->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be omitted, \'ssl\', or \'tls\'', true) + ->param('autoTLS', true, new Boolean(), 'Enable SMTP AutoTLS feature.', true) + ->param('mailer', '', new Text(0), 'The value to use for the X-Mailer header.', true) + ->param('fromName', '', new Text(128, 0), 'Sender Name.', true) + ->param('fromEmail', '', new Email(), 'Sender email address.', true) + ->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true) + ->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->inject('queueForEvents') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, string $host, int $port, string $username, string $password, string $encryption, bool $autoTLS, string $mailer, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + + $credentials = [ + 'port' => $port, + 'username' => $username, + 'password' => $password, + ]; + + if (!empty($host)) { + $credentials['host'] = $host; + } + + $options = [ + 'fromName' => $fromName, + 'fromEmail' => $fromEmail, + 'replyToName' => $replyToName, + 'replyToEmail' => $replyToEmail, + 'encryption' => $encryption === 'none' ? '' : $encryption, + 'autoTLS' => $autoTLS, + 'mailer' => $mailer, + ]; + + if ( + $enabled === true + && !empty($fromEmail) + && \array_key_exists('host', $credentials) + ) { + $enabled = true; + } else { + $enabled = false; + } + + $provider = new Document([ + '$id' => $providerId, + 'name' => $name, + 'provider' => 'smtp', + 'type' => MESSAGE_TYPE_EMAIL, + 'enabled' => $enabled, + 'credentials' => $credentials, + 'options' => $options, + ]); + + try { + $provider = $dbForProject->createDocument('providers', $provider); + } catch (DuplicateException) { + throw new Exception(Exception::PROVIDER_ALREADY_EXISTS); + } + + $queueForEvents + ->setParam('providerId', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + App::post('/v1/messaging/providers/msg91') ->desc('Create Msg91 provider') ->groups(['api', 'messaging']) @@ -616,9 +699,13 @@ App::post('/v1/messaging/providers/fcm') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?array $serviceAccountJSON, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, array|string|null $serviceAccountJSON, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; + $serviceAccountJSON = \is_string($serviceAccountJSON) + ? \json_decode($serviceAccountJSON, true) + : $serviceAccountJSON; + $credentials = []; if (!\is_null($serviceAccountJSON)) { @@ -757,7 +844,7 @@ App::get('/v1/messaging/providers') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { @@ -917,9 +1004,10 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') if ($provider->isEmpty()) { throw new Exception(Exception::PROVIDER_NOT_FOUND); } - $providerAttr = $provider->getAttribute('provider'); - if ($providerAttr !== 'mailgun') { + $providerProvider = $provider->getAttribute('provider'); + + if ($providerProvider !== 'mailgun') { throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } @@ -949,7 +1037,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $credentials = $provider->getAttribute('credentials'); - if ($isEuRegion === true || $isEuRegion === false) { + if (!\is_null($isEuRegion)) { $credentials['isEuRegion'] = $isEuRegion; } @@ -963,19 +1051,21 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true && - \array_key_exists('isEuRegion', $credentials) && - \array_key_exists('apiKey', $credentials) && - \array_key_exists('domain', $credentials) && - \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('isEuRegion', $credentials) && + \array_key_exists('apiKey', $credentials) && + \array_key_exists('domain', $credentials) && + \array_key_exists('fromEmail', $options) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1054,17 +1144,138 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ]); } - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('apiKey', $provider->getAttribute('credentials')) - && \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('apiKey', $provider->getAttribute('credentials')) && + \array_key_exists('fromEmail', $provider->getAttribute('options')) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + + $queueForEvents + ->setParam('providerId', $provider->getId()); + + $response + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/smtp/:providerId') + ->desc('Update SMTP provider') + ->groups(['api', 'messaging']) + ->label('audits.event', 'provider.update') + ->label('audits.resource', 'provider/{response.$id}') + ->label('event', 'providers.[providerId].update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateSMTPProvider') + ->label('sdk.description', '/docs/references/messaging/update-smtp-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('providerId', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host by using this format: [hostname:port] (e.g. "smtp1.example.com:25;smtp2.example.com"). You can also specify encryption type, for example: (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). Hosts will be tried in order.', true) + ->param('port', null, new Range(1, 65535), 'SMTP port.', true) + ->param('username', '', new Text(0), 'Authentication username.', true) + ->param('password', '', new Text(0), 'Authentication password.', true) + ->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be \'ssl\' or \'tls\'', true) + ->param('autoTLS', null, new Boolean(), 'Enable SMTP AutoTLS feature.', true) + ->param('fromName', '', new Text(128), 'Sender Name.', true) + ->param('fromEmail', '', new Email(), 'Sender email address.', true) + ->param('replyToName', '', new Text(128), 'Name set in the Reply To field for the mail. Default value is Sender Name.', true) + ->param('replyToEmail', '', new Text(128), 'Email set in the Reply To field for the mail. Default value is Sender Email.', true) + ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->inject('queueForEvents') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $providerId, string $name, string $host, ?int $port, string $username, string $password, string $encryption, ?bool $autoTLS, string $fromName, string $fromEmail, string $replyToName, string $replyToEmail, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $providerId); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'smtp') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); + } + + if (!empty($name)) { + $provider->setAttribute('name', $name); + } + + $options = $provider->getAttribute('options'); + + if (!empty($fromName)) { + $options['fromName'] = $fromName; + } + + if (!empty($fromEmail)) { + $options['fromEmail'] = $fromEmail; + } + + if (!empty($replyToName)) { + $options['replyToName'] = $replyToName; + } + + if (!empty($replyToEmail)) { + $options['replyToEmail'] = $replyToEmail; + } + + $provider->setAttribute('options', $options); + + $credentials = $provider->getAttribute('credentials'); + + if (!empty($host)) { + $credentials['host'] = $host; + } + + if (!\is_null($port)) { + $credentials['port'] = $port; + } + + if (!empty($username)) { + $credentials['username'] = $username; + } + + if (!empty($password)) { + $credentials['password'] = $password; + } + + if (!empty($encryption)) { + $credentials['encryption'] = $encryption === 'none' ? '' : $encryption; + } + + if (!\is_null($autoTLS)) { + $credentials['autoTLS'] = $autoTLS; + } + + $provider->setAttribute('credentials', $credentials); + + if (!\is_null($enabled)) { + if ($enabled) { + if ( + !empty($options['fromEmail']) + && \array_key_exists('host', $credentials) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } + } else { + $provider->setAttribute('enabled', false); } - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1133,18 +1344,20 @@ App::patch('/v1/messaging/providers/msg91/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('senderId', $credentials) - && \array_key_exists('authKey', $credentials) - && \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('senderId', $credentials) && + \array_key_exists('authKey', $credentials) && + \array_key_exists('from', $provider->getAttribute('options')) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1213,19 +1426,20 @@ App::patch('/v1/messaging/providers/telesign/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('username', $credentials) - && \array_key_exists('password', $credentials) - && \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('username', $credentials) && + \array_key_exists('password', $credentials) && + \array_key_exists('from', $provider->getAttribute('options')) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1294,19 +1508,20 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('username', $credentials) - && \array_key_exists('apiKey', $credentials) - && \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('username', $credentials) && + \array_key_exists('apiKey', $credentials) && + \array_key_exists('from', $provider->getAttribute('options')) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1375,19 +1590,20 @@ App::patch('/v1/messaging/providers/twilio/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('accountSid', $credentials) - && \array_key_exists('authToken', $credentials) - && \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('accountSid', $credentials) && + \array_key_exists('authToken', $credentials) && + \array_key_exists('from', $provider->getAttribute('options')) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1456,19 +1672,20 @@ App::patch('/v1/messaging/providers/vonage/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('apiKey', $credentials) - && \array_key_exists('apiSecret', $credentials) - && \array_key_exists('from', $provider->getAttribute('options')) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('apiKey', $credentials) && + \array_key_exists('apiSecret', $credentials) && + \array_key_exists('from', $provider->getAttribute('options')) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1501,7 +1718,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?array $serviceAccountJSON, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, array|string|null $serviceAccountJSON, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1518,17 +1735,25 @@ App::patch('/v1/messaging/providers/fcm/:providerId') } if (!\is_null($serviceAccountJSON)) { - $provider->setAttribute('credentials', ['serviceAccountJSON' => $serviceAccountJSON]); + $serviceAccountJSON = \is_string($serviceAccountJSON) + ? \json_decode($serviceAccountJSON, true) + : $serviceAccountJSON; + + $provider->setAttribute('credentials', [ + 'serviceAccountJSON' => $serviceAccountJSON + ]); } - if ($enabled === true || $enabled === false) { - if ($enabled === true && \array_key_exists('serviceAccountJSON', $provider->getAttribute('credentials'))) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if (\array_key_exists('serviceAccountJSON', $provider->getAttribute('credentials'))) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1601,20 +1826,21 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('credentials', $credentials); - if ($enabled === true || $enabled === false) { - if ( - $enabled === true - && \array_key_exists('authKey', $credentials) - && \array_key_exists('authKeyId', $credentials) - && \array_key_exists('teamId', $credentials) - && \array_key_exists('bundleId', $credentials) - ) { - $enabled = true; + if (!\is_null($enabled)) { + if ($enabled) { + if ( + \array_key_exists('authKey', $credentials) && + \array_key_exists('authKeyId', $credentials) && + \array_key_exists('teamId', $credentials) && + \array_key_exists('bundleId', $credentials) + ) { + $provider->setAttribute('enabled', true); + } else { + throw new Exception(Exception::PROVIDER_MISSING_CREDENTIALS); + } } else { - $enabled = false; + $provider->setAttribute('enabled', false); } - - $provider->setAttribute('enabled', $enabled); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); @@ -1677,17 +1903,15 @@ App::post('/v1/messaging/topics') ->label('sdk.response.model', Response::MODEL_TOPIC) ->param('topicId', '', new CustomId(), 'Topic ID. Choose a custom Topic ID or a new Topic ID.') ->param('name', '', new Text(128), 'Topic Name.') - ->param('description', '', new Text(2048), 'Topic Description.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, string $name, string $description, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $topicId, string $name, Event $queueForEvents, Database $dbForProject, Response $response) { $topicId = $topicId == 'unique()' ? ID::unique() : $topicId; $topic = new Document([ '$id' => $topicId, 'name' => $name, - 'description' => $description ]); try { @@ -1727,7 +1951,7 @@ App::get('/v1/messaging/topics') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { @@ -1874,11 +2098,10 @@ 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', '', new Text(2048), 'Topic Description.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, string $name, string $description, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $topicId, string $name, Event $queueForEvents, Database $dbForProject, Response $response) { $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { @@ -1889,10 +2112,6 @@ App::patch('/v1/messaging/topics/:topicId') $topic->setAttribute('name', $name); } - if (!empty($description)) { - $topic->setAttribute('description', $description); - } - $topic = $dbForProject->updateDocument('topics', $topicId, $topic); $queueForEvents @@ -2054,7 +2273,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') \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 = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { @@ -2277,7 +2496,6 @@ App::post('/v1/messaging/messages/email') ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true) ->param('cc', [], new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true) ->param('bcc', [], new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true) - ->param('description', '', new Text(256), 'Description for message.', true) ->param('status', MessageStatus::DRAFT, new WhiteList([MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]), 'Message Status. Value must be one of: ' . implode(', ', [MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]) . '.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) @@ -2287,7 +2505,7 @@ App::post('/v1/messaging/messages/email') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, string $description, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2326,7 +2544,6 @@ App::post('/v1/messaging/messages/email') 'topics' => $topics, 'users' => $users, 'targets' => $targets, - 'description' => $description, 'scheduledAt' => $scheduledAt, 'data' => [ 'subject' => $subject, @@ -2396,7 +2613,6 @@ App::post('/v1/messaging/messages/sms') ->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true) ->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true) ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true) - ->param('description', '', new Text(256), 'Description for Message.', true) ->param('status', MessageStatus::DRAFT, new WhiteList([MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]), 'Message Status. Value must be one of: ' . implode(', ', [MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]) . '.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') @@ -2405,7 +2621,7 @@ App::post('/v1/messaging/messages/sms') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $content, array $topics, array $users, array $targets, string $description, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $content, array $topics, array $users, array $targets, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2442,7 +2658,6 @@ App::post('/v1/messaging/messages/sms') 'topics' => $topics, 'users' => $users, 'targets' => $targets, - 'description' => $description, 'data' => [ 'content' => $content, ], @@ -2508,7 +2723,6 @@ App::post('/v1/messaging/messages/push') ->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true) ->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true) ->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true) - ->param('description', '', new Text(256), 'Description for Message.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) ->param('action', '', new Text(256), 'Action for push notification.', true) ->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true) @@ -2524,7 +2738,7 @@ App::post('/v1/messaging/messages/push') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, string $description, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2571,7 +2785,6 @@ App::post('/v1/messaging/messages/push') 'topics' => $topics, 'users' => $users, 'targets' => $targets, - 'description' => $description, 'scheduledAt' => $scheduledAt, 'data' => $pushData, 'status' => $status, @@ -2639,7 +2852,7 @@ App::get('/v1/messaging/messages') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { @@ -2782,7 +2995,7 @@ App::get('/v1/messaging/messages/:messageId/targets') $queries[] = Query::equal('$id', $targetIDs); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { @@ -2845,7 +3058,6 @@ App::patch('/v1/messaging/messages/email/:messageId') ->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true) ->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true) ->param('subject', null, new Text(998), 'Email Subject.', true) - ->param('description', null, new Text(256), 'Description for Message.', true) ->param('content', null, new Text(64230), 'Email Content.', true) ->param('status', MessageStatus::DRAFT, new WhiteList([MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]), 'Message Status. Value must be one of: ' . implode(', ', [MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]) . '.', true) ->param('html', null, new Boolean(), 'Is content of type HTML', true) @@ -2858,7 +3070,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $description, ?string $content, ?string $status, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?string $status, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -2909,10 +3121,6 @@ App::patch('/v1/messaging/messages/email/:messageId') $message->setAttribute('data', $data); - if (!\is_null($description)) { - $message->setAttribute('description', $description); - } - if (!\is_null($status)) { $message->setAttribute('status', $status); } @@ -2983,7 +3191,6 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true) ->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true) ->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true) - ->param('description', null, new Text(256), 'Description for Message.', true) ->param('content', null, new Text(64230), 'Email Content.', true) ->param('status', null, new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) @@ -2993,7 +3200,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $description, ?string $content, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3032,10 +3239,6 @@ App::patch('/v1/messaging/messages/sms/:messageId') $message->setAttribute('status', $status); } - if (!\is_null($description)) { - $message->setAttribute('description', $description); - } - if (!\is_null($scheduledAt)) { if (\is_null($message->getAttribute(('scheduleId')))) { $schedule = $dbForConsole->createDocument('schedules', new Document([ @@ -3102,7 +3305,6 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true) ->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true) ->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true) - ->param('description', null, new Text(256), 'Description for Message.', true) ->param('title', null, new Text(256), 'Title for push notification.', true) ->param('body', null, new Text(64230), 'Body for push notification.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) @@ -3120,7 +3322,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $description, ?string $title, ?string $body, ?array $data, ?string $action, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3191,10 +3393,6 @@ App::patch('/v1/messaging/messages/push/:messageId') $message->setAttribute('status', $status); } - if (!\is_null($description)) { - $message->setAttribute('description', $description); - } - if (!\is_null($scheduledAt)) { if (\is_null($message->getAttribute(('scheduleId')))) { $schedule = $dbForConsole->createDocument('schedules', new Document([ @@ -3242,3 +3440,59 @@ App::patch('/v1/messaging/messages/push/:messageId') $response ->dynamic($message, Response::MODEL_MESSAGE); }); + +App::delete('/v1/messaging/messages/:messageId') + ->desc('Delete a message') + ->groups(['api', 'messaging']) + ->label('audits.event', 'message.delete') + ->label('audits.resource', 'message/{request.route.messageId}') + ->label('event', 'messages.[messageId].delete') + ->label('scope', 'messages.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'delete') + ->label('sdk.description', '/docs/references/messaging/delete-message.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('messageId', '', new UID(), 'Message ID.') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->action(function (string $messageId, Database $dbForProject, Database $dbForConsole, Response $response) { + $message = $dbForProject->getDocument('messages', $messageId); + + if ($message->isEmpty()) { + throw new Exception(Exception::MESSAGE_NOT_FOUND); + } + + switch ($message->getAttribute('status')) { + case MessageStatus::PROCESSING: + throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); + case MessageStatus::SCHEDULED: + $scheduleId = $message->getAttribute('scheduleId'); + $scheduledAt = $message->getAttribute('scheduledAt'); + + $now = DateTime::now(); + $scheduledDate = DateTime::formatTz($scheduledAt); + + if ($now > $scheduledDate) { + throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); + } + + if (!empty($scheduleId)) { + try { + $dbForConsole->deleteDocument('schedules', $scheduleId); + } catch (Exception) { + // Ignore + } + } + break; + default: + break; + } + + $dbForProject->deleteDocument('messages', $message->getId()); + + $response->noContent(); + }); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 65a0520d1c..87d3c12c99 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -392,7 +392,7 @@ App::get('/v1/migrations') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 01a7c26e77..30f6b1f376 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -249,7 +249,7 @@ App::get('/v1/projects') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -825,7 +825,7 @@ App::post('/v1/projects/:projectId/webhooks') $webhook = $dbForConsole->createDocument('webhooks', $webhook); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -952,7 +952,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') } $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); @@ -991,7 +991,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') $webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64))); $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); @@ -1028,7 +1028,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') $dbForConsole->deleteDocument('webhooks', $webhook->getId()); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->noContent(); }); @@ -1078,7 +1078,7 @@ App::post('/v1/projects/:projectId/keys') $key = $dbForConsole->createDocument('keys', $key); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1192,7 +1192,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') $dbForConsole->updateDocument('keys', $key->getId(), $key); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->dynamic($key, Response::MODEL_KEY); }); @@ -1229,7 +1229,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') $dbForConsole->deleteDocument('keys', $key->getId()); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->noContent(); }); @@ -1279,7 +1279,7 @@ App::post('/v1/projects/:projectId/platforms') $platform = $dbForConsole->createDocument('platforms', $platform); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1394,7 +1394,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') $dbForConsole->updateDocument('platforms', $platform->getId(), $platform); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->dynamic($platform, Response::MODEL_PLATFORM); }); @@ -1431,7 +1431,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') $dbForConsole->deleteDocument('platforms', $platformId); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $response->noContent(); }); diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 23916a114c..3081b3def3 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -164,7 +164,7 @@ App::get('/v1/proxy/rules') $queries[] = Query::equal('projectInternalId', [$project->getInternalId()]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index efe2515468..fc5d00dfd0 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -169,7 +169,7 @@ App::get('/v1/storage/buckets') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -451,7 +451,7 @@ App::post('/v1/storage/buckets/:bucketId/files') } $idValidator = new UID(); - if (!$idValidator->isValid($request->getHeader('x-appwrite-id'))) { + if (!$idValidator->isValid($fileId)) { throw new Exception(Exception::STORAGE_INVALID_APPWRITE_ID); } @@ -745,7 +745,7 @@ App::get('/v1/storage/buckets/:bucketId/files') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -963,7 +963,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') break; } - $image = new Image($source); + try { + $image = new Image($source); + } catch (ImagickException $e) { + throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED, $e->getMessage()); + } $image->crop((int) $width, (int) $height, $gravity); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 98ad9ddab9..685c230f4c 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -114,7 +114,7 @@ App::post('/v1/teams') ]); $membership = $dbForProject->createDocument('memberships', $membership); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } $queueForEvents->setParam('teamId', $team->getId()); @@ -154,7 +154,7 @@ App::get('/v1/teams') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -537,7 +537,7 @@ App::post('/v1/teams/:teamId/memberships') $team->setAttribute('total', $team->getAttribute('total', 0) + 1); $team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team)); - $dbForProject->deleteCachedDocument('users', $invitee->getId()); + $dbForProject->purgeCachedDocument('users', $invitee->getId()); } else { try { $membership = $dbForProject->createDocument('memberships', $membership); @@ -710,7 +710,7 @@ App::get('/v1/teams/:teamId/memberships') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -857,7 +857,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') /** * Replace membership on profile */ - $dbForProject->deleteCachedDocument('users', $profile->getId()); + $dbForProject->purgeCachedDocument('users', $profile->getId()); $queueForEvents ->setParam('teamId', $team->getId()) @@ -974,13 +974,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Permission::delete(Role::user($user->getId())), ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); Authorization::setRole(Role::user($userId)->toString()); $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1))); @@ -1059,7 +1059,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB'); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); if ($membership->getAttribute('confirm')) { // Count only confirmed members $team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0)); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 33b014650b..dd852430fd 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -145,7 +145,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e } } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -508,7 +508,7 @@ App::post('/v1/users/:userId/targets') } catch (Duplicate) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -544,7 +544,7 @@ App::get('/v1/users') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -839,7 +839,7 @@ App::get('/v1/users/:userId/targets') $queries[] = Query::equal('userId', [$userId]); // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { @@ -884,7 +884,7 @@ App::get('/v1/users/identities') // Get cursor document if there was a cursor query $cursor = \array_filter($queries, function ($query) { - return \in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); $cursor = reset($cursor); if ($cursor) { @@ -958,7 +958,7 @@ App::put('/v1/users/:userId/labels') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new UID(), 'User ID.') - ->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of user labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.') + ->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_LABELS_SIZE), 'Array of user labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') @@ -1184,7 +1184,7 @@ App::patch('/v1/users/:userId/email') if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -1246,7 +1246,7 @@ App::patch('/v1/users/:userId/phone') if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number)); } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS); } @@ -1408,7 +1408,7 @@ App::patch('/v1/users/:userId/targets/:targetId') } $target = $dbForProject->updateDocument('targets', $target->getId(), $target); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -1657,7 +1657,7 @@ App::post('/v1/users/:userId/tokens') ]); $token = $dbForProject->createDocument('tokens', $token); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $token->setAttribute('secret', $secret); @@ -1704,7 +1704,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') } $dbForProject->deleteDocument('sessions', $session->getId()); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -1747,7 +1747,7 @@ App::delete('/v1/users/:userId/sessions') //TODO: fix this } - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents ->setParam('userId', $user->getId()) @@ -1815,10 +1815,10 @@ App::delete('/v1/users/:userId/targets/:targetId') ->param('userId', '', new UID(), 'User ID.') ->param('targetId', '', new UID(), 'Target ID.') ->inject('queueForEvents') + ->inject('queueForDeletes') ->inject('response') ->inject('dbForProject') - ->action(function (string $userId, string $targetId, Event $queueForEvents, Response $response, Database $dbForProject) { - + ->action(function (string $userId, string $targetId, Event $queueForEvents, Delete $queueForDeletes, Response $response, Database $dbForProject) { $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty()) { @@ -1836,7 +1836,11 @@ App::delete('/v1/users/:userId/targets/:targetId') } $dbForProject->deleteDocument('targets', $target->getId()); - $dbForProject->deleteCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); + + $queueForDeletes + ->setType(DELETE_TYPE_TARGET) + ->setDocument($target); $queueForEvents ->setParam('userId', $user->getId()) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 1b0c993e11..df18320cf2 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -978,7 +978,7 @@ App::get('/v1/vcs/installations') } // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ diff --git a/app/controllers/general.php b/app/controllers/general.php index ebb2a31891..74f12017b1 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -510,7 +510,7 @@ App::init() if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) { $key->setAttribute('accessedAt', DateTime::now()); $dbForConsole->updateDocument('keys', $key->getId(), $key); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); } $sdkValidator = new WhiteList($servers, true); @@ -524,7 +524,7 @@ App::init() /** Update access time as well */ $key->setAttribute('accessedAt', Datetime::now()); $dbForConsole->updateDocument('keys', $key->getId(), $key); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); } } } @@ -753,7 +753,7 @@ App::error() 'code' => $code, 'file' => $file, 'line' => $line, - 'trace' => $trace, + 'trace' => \json_encode($trace, JSON_UNESCAPED_UNICODE) === false ? [] : $trace, // check for failing encode 'version' => $version, 'type' => $type, ] : [ diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 7a22af5016..d6cc702e75 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -410,7 +410,7 @@ App::shutdown() $session = array_shift($sessions); $dbForProject->deleteDocument('sessions', $session->getId()); } - $dbForProject->deleteCachedDocument('users', $userId); + $dbForProject->purgeCachedDocument('users', $userId); }); App::shutdown() diff --git a/app/http.php b/app/http.php index 554b8b6f8f..0b17ae5b83 100644 --- a/app/http.php +++ b/app/http.php @@ -148,7 +148,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $dbForConsole->createCollection($key, $attributes, $indexes); } - if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDefaultDatabase(), 'bucket_1')) { + if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) { Console::success('[Setup] - Creating default bucket...'); $dbForConsole->createDocument('buckets', new Document([ '$id' => ID::custom('default'), diff --git a/app/init.php b/app/init.php index 1ab0976aef..16a33955fd 100644 --- a/app/init.php +++ b/app/init.php @@ -34,6 +34,7 @@ use Appwrite\Network\Validator\Origin; use Appwrite\OpenSSL\OpenSSL; use Appwrite\URL\URL as AppwriteURL; use Utopia\App; +use Utopia\Database\Adapter\SQL; use Utopia\Logger\Logger; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Cache; @@ -99,6 +100,7 @@ const APP_LIMIT_ANTIVIRUS = 20_000_000; //20MB const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB const APP_LIMIT_COMPRESSION = 20_000_000; //20MB const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value +const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length. const APP_LIMIT_SUBQUERY = 1000; const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000; @@ -174,6 +176,7 @@ const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource'; const DELETE_TYPE_SCHEDULES = 'schedules'; const DELETE_TYPE_TOPIC = 'topic'; const DELETE_TYPE_TARGET = 'target'; +const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets'; // Mail Types const MAIL_TYPE_VERIFICATION = 'verification'; const MAIL_TYPE_MAGIC_SESSION = 'magicSession'; @@ -817,10 +820,10 @@ $register->set('pools', function () { $resource = function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array( + // No need to set PDO::ATTR_ERRMODE it is overwitten in PDOProxy PDO::ATTR_TIMEOUT => 3, // Seconds PDO::ATTR_PERSISTENT => true, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed PDO::ATTR_EMULATE_PREPARES => true, PDO::ATTR_STRINGIFY_FETCHES => true )); @@ -842,12 +845,10 @@ $register->set('pools', function () { default: throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid scheme"); - break; } $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) { // Get Adapter - $adapter = null; switch ($type) { case 'database': $adapter = match ($dsn->getScheme()) { @@ -856,7 +857,7 @@ $register->set('pools', function () { default => null }; - $adapter->setDefaultDatabase($dsn->getPath()); + $adapter->setDatabase($dsn->getPath()); break; case 'pubsub': $adapter = $resource(); @@ -876,7 +877,6 @@ $register->set('pools', function () { default: throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation."); - break; } return $adapter; @@ -893,22 +893,18 @@ $register->set('pools', function () { $register->set('db', function () { // This is usually for our workers or CLI commands scope - $dbHost = App::getEnv('_APP_DB_HOST', ''); - $dbPort = App::getEnv('_APP_DB_PORT', ''); - $dbUser = App::getEnv('_APP_DB_USER', ''); - $dbPass = App::getEnv('_APP_DB_PASS', ''); - $dbScheme = App::getEnv('_APP_DB_SCHEMA', ''); + $dbHost = App::getEnv('_APP_DB_HOST', ''); + $dbPort = App::getEnv('_APP_DB_PORT', ''); + $dbUser = App::getEnv('_APP_DB_USER', ''); + $dbPass = App::getEnv('_APP_DB_PASS', ''); + $dbScheme = App::getEnv('_APP_DB_SCHEMA', ''); - $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array( - PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_EMULATE_PREPARES => true, - PDO::ATTR_STRINGIFY_FETCHES => true, - )); - - return $pdo; + return new PDO( + "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", + $dbUser, + $dbPass, + SQL::getPDOAttributes() + ); }); $register->set('smtp', function () { diff --git a/composer.json b/composer.json index 25485af6a2..e1b861054c 100644 --- a/composer.json +++ b/composer.json @@ -43,20 +43,20 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.13.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "0.33.*", + "utopia-php/abuse": "0.36.*", "utopia-php/analytics": "0.10.*", - "utopia-php/audit": "0.35.*", + "utopia-php/audit": "0.38.*", "utopia-php/cache": "0.9.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.45.*", + "utopia-php/database": "0.48.*", "utopia-php/domains": "0.5.*", - "utopia-php/dsn": "0.1.*", + "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", - "utopia-php/image": "0.5.*", + "utopia-php/image": "0.6.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.3.*", - "utopia-php/messaging": "0.8.*", + "utopia-php/messaging": "0.9.*", "utopia-php/migration": "0.3.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.5.*", @@ -75,7 +75,7 @@ "adhocore/jwt": "1.1.2", "spomky-labs/otphp": "^10.0", "webonyx/graphql-php": "14.11.*", - "league/csv": "9.7.1" + "league/csv": "^9.14" }, "repositories": [ { @@ -97,7 +97,7 @@ }, "config": { "platform": { - "php": "8.0" + "php": "8.2" } } } diff --git a/composer.lock b/composer.lock index 74706dc80f..17800352ad 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": "6ccadcec8172eb800888228b5f653f08", + "content-hash": "3b43bf6f0fca50a3a2834e1bbaa90d63", "packages": [ { "name": "adhocore/jwt", @@ -530,34 +530,39 @@ }, { "name": "league/csv", - "version": "9.7.1", + "version": "9.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1" + "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/0ec57e8264ec92565974ead0d1724cf1026e10c1", - "reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/34bf0df7340b60824b9449b5c526fcc3325070d5", + "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5", "shasum": "" }, "require": { + "ext-filter": "*", "ext-json": "*", "ext-mbstring": "*", - "php": "^7.3 || ^8.0" + "php": "^8.1.2" }, "require-dev": { - "ext-curl": "*", + "doctrine/collections": "^2.1.4", "ext-dom": "*", - "friendsofphp/php-cs-fixer": "^2.16", - "phpstan/phpstan": "^0.12.0", - "phpstan/phpstan-phpunit": "^0.12.0", - "phpstan/phpstan-strict-rules": "^0.12.0", - "phpunit/phpunit": "^9.5" + "ext-xdebug": "*", + "friendsofphp/php-cs-fixer": "^v3.22.0", + "phpbench/phpbench": "^1.2.15", + "phpstan/phpstan": "^1.10.50", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "phpunit/phpunit": "^10.5.3", + "symfony/var-dumper": "^6.4.0" }, "suggest": { - "ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes", + "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" }, "type": "library", @@ -587,7 +592,7 @@ } ], "description": "CSV data manipulation made easy in PHP", - "homepage": "http://csv.thephpleague.com", + "homepage": "https://csv.thephpleague.com", "keywords": [ "convert", "csv", @@ -610,7 +615,7 @@ "type": "github" } ], - "time": "2021-04-17T16:32:08+00:00" + "time": "2023-12-29T07:34:53+00:00" }, { "name": "matomo/device-detector", @@ -1246,23 +1251,23 @@ }, { "name": "utopia-php/abuse", - "version": "0.33.0", + "version": "0.36.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "1ba8d5f2793885cbf779e3b5b9d886968af43d2c" + "reference": "d3d09b4fa0db75935110714ad4b2a87f3ace31ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/1ba8d5f2793885cbf779e3b5b9d886968af43d2c", - "reference": "1ba8d5f2793885cbf779e3b5b9d886968af43d2c", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/d3d09b4fa0db75935110714ad4b2a87f3ace31ed", + "reference": "d3d09b4fa0db75935110714ad4b2a87f3ace31ed", "shasum": "" }, "require": { "ext-curl": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": "0.45.*" + "utopia-php/database": "0.48.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1289,9 +1294,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.33.0" + "source": "https://github.com/utopia-php/abuse/tree/0.36.0" }, - "time": "2023-11-01T08:51:33+00:00" + "time": "2024-01-19T09:32:56+00:00" }, { "name": "utopia-php/analytics", @@ -1341,21 +1346,21 @@ }, { "name": "utopia-php/audit", - "version": "0.35.0", + "version": "0.38.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "ed9366ef05556da040de7a8b570f4160c7d8ea4a" + "reference": "a9067f4af76e8787f1d29850a8ec94fc32bb6539" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/ed9366ef05556da040de7a8b570f4160c7d8ea4a", - "reference": "ed9366ef05556da040de7a8b570f4160c7d8ea4a", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/a9067f4af76e8787f1d29850a8ec94fc32bb6539", + "reference": "a9067f4af76e8787f1d29850a8ec94fc32bb6539", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.45.*" + "utopia-php/database": "0.48.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1382,9 +1387,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.35.0" + "source": "https://github.com/utopia-php/audit/tree/0.38.0" }, - "time": "2023-11-01T08:51:29+00:00" + "time": "2024-01-19T09:33:05+00:00" }, { "name": "utopia-php/cache", @@ -1538,16 +1543,16 @@ }, { "name": "utopia-php/database", - "version": "0.45.5", + "version": "0.48.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0b66a017f817a910acb83e6aea92bccea9571fe6" + "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0b66a017f817a910acb83e6aea92bccea9571fe6", - "reference": "0b66a017f817a910acb83e6aea92bccea9571fe6", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2651f41b9d3909dc123d26becfb6a3a44fb63077", + "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077", "shasum": "" }, "require": { @@ -1588,9 +1593,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.45.5" + "source": "https://github.com/utopia-php/database/tree/0.48.0" }, - "time": "2024-01-08T17:08:15+00:00" + "time": "2024-01-19T08:17:22+00:00" }, { "name": "utopia-php/domains", @@ -1654,16 +1659,16 @@ }, { "name": "utopia-php/dsn", - "version": "0.1.0", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/dsn.git", - "reference": "17a5935eab1b89fb4b95600db50a1b6d5faa6cea" + "reference": "c11f37a12c3f6aaf9fea97ca7cb363dcc93668d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/dsn/zipball/17a5935eab1b89fb4b95600db50a1b6d5faa6cea", - "reference": "17a5935eab1b89fb4b95600db50a1b6d5faa6cea", + "url": "https://api.github.com/repos/utopia-php/dsn/zipball/c11f37a12c3f6aaf9fea97ca7cb363dcc93668d7", + "reference": "c11f37a12c3f6aaf9fea97ca7cb363dcc93668d7", "shasum": "" }, "require": { @@ -1695,22 +1700,22 @@ ], "support": { "issues": "https://github.com/utopia-php/dsn/issues", - "source": "https://github.com/utopia-php/dsn/tree/0.1.0" + "source": "https://github.com/utopia-php/dsn/tree/0.2.0" }, - "time": "2022-10-26T10:06:20+00:00" + "time": "2023-11-02T12:01:43+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.1", + "version": "0.33.2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "b745607aa1875554a0ad52e28f6db918da1ce11c" + "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/b745607aa1875554a0ad52e28f6db918da1ce11c", - "reference": "b745607aa1875554a0ad52e28f6db918da1ce11c", + "url": "https://api.github.com/repos/utopia-php/http/zipball/b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", + "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", "shasum": "" }, "require": { @@ -1740,22 +1745,22 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.1" + "source": "https://github.com/utopia-php/http/tree/0.33.2" }, - "time": "2024-01-17T16:48:32+00:00" + "time": "2024-01-31T10:35:59+00:00" }, { "name": "utopia-php/image", - "version": "0.5.4", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "ca5f436f9aa22dedaa6648f24f3687733808e336" + "reference": "88f7209172bdabd81e76ac981c95fac117dc6e08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/ca5f436f9aa22dedaa6648f24f3687733808e336", - "reference": "ca5f436f9aa22dedaa6648f24f3687733808e336", + "url": "https://api.github.com/repos/utopia-php/image/zipball/88f7209172bdabd81e76ac981c95fac117dc6e08", + "reference": "88f7209172bdabd81e76ac981c95fac117dc6e08", "shasum": "" }, "require": { @@ -1763,6 +1768,8 @@ "php": ">=8.0" }, "require-dev": { + "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -1776,12 +1783,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "description": "A simple Image manipulation library", "keywords": [ "framework", @@ -1792,9 +1793,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.5.4" + "source": "https://github.com/utopia-php/image/tree/0.6.0" }, - "time": "2022-05-11T12:30:41+00:00" + "time": "2024-01-24T06:59:44+00:00" }, { "name": "utopia-php/locale", @@ -1902,16 +1903,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.8.1", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "bfb5014d3a8752901e50da1ae21bf309a6af5006" + "reference": "df54ba51570e886724590edeb03dbd455bb0464d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/bfb5014d3a8752901e50da1ae21bf309a6af5006", - "reference": "bfb5014d3a8752901e50da1ae21bf309a6af5006", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/df54ba51570e886724590edeb03dbd455bb0464d", + "reference": "df54ba51570e886724590edeb03dbd455bb0464d", "shasum": "" }, "require": { @@ -1946,9 +1947,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.8.1" + "source": "https://github.com/utopia-php/messaging/tree/0.9.0" }, - "time": "2024-01-10T23:55:03+00:00" + "time": "2024-01-31T11:51:27+00:00" }, { "name": "utopia-php/migration", @@ -2821,16 +2822,16 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", "shasum": "" }, "require": { @@ -2862,9 +2863,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.2" + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" }, - "time": "2023-09-27T20:04:15+00:00" + "time": "2024-01-30T19:34:25+00:00" }, { "name": "doctrine/instantiator", @@ -5520,7 +5521,7 @@ "ext-fileinfo": "*" }, "platform-overrides": { - "php": "8.0" + "php": "8.2" }, "plugin-api-version": "2.3.0" } diff --git a/dev/xdebug.ini b/dev/xdebug.ini new file mode 100644 index 0000000000..e29c8bd46e --- /dev/null +++ b/dev/xdebug.ini @@ -0,0 +1,6 @@ +zend_extension=xdebug + +[xdebug] +xdebug.mode=develop,debug +xdebug.client_host=host.docker.internal +xdebug.start_with_request=yes \ No newline at end of file diff --git a/dev/yasd_init.php.stub b/dev/yasd_init.php.stub deleted file mode 100644 index efcc37c0b5..0000000000 --- a/dev/yasd_init.php.stub +++ /dev/null @@ -1,4 +0,0 @@ -strict) { $password = strtolower($password); - $this->userId = strtolower($this->userId); - $this->email = strtolower($this->email); - $this->name = strtolower($this->name); - $this->phone = strtolower($this->phone); + $this->userId = strtolower($this->userId ?? ''); + $this->email = strtolower($this->email ?? ''); + $this->name = strtolower($this->name ?? ''); + $this->phone = strtolower($this->phone ?? ''); } if ($this->userId && strpos($password, $this->userId) !== false) { diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index c63da76e83..fd8b1911a5 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -249,7 +249,7 @@ class Exception extends \Exception public const PROVIDER_NOT_FOUND = 'provider_not_found'; public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists'; public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; - public const PROVIDER_INTERNAL_UPDATE_DISABLED = 'provider_internal_update_disabled'; + public const PROVIDER_MISSING_CREDENTIALS = 'provider_missing_credentials'; /** Topic */ public const TOPIC_NOT_FOUND = 'topic_not_found'; diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 31f530bd75..a0529c5aef 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -397,7 +397,7 @@ abstract class Migration */ protected function changeAttributeInternalType(string $collection, string $attribute, string $type): void { - $stmt = $this->pdo->prepare("ALTER TABLE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$collection}` MODIFY `$attribute` $type;"); + $stmt = $this->pdo->prepare("ALTER TABLE `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$collection}` MODIFY `$attribute` $type;"); try { $stmt->execute(); diff --git a/src/Appwrite/Migration/Version/V15.php b/src/Appwrite/Migration/Version/V15.php index 1a8e8a4265..aaa8fd2373 100644 --- a/src/Appwrite/Migration/Version/V15.php +++ b/src/Appwrite/Migration/Version/V15.php @@ -295,7 +295,7 @@ class V15 extends Migration protected function removeWritePermissions(string $table): void { try { - $this->pdo->prepare("DELETE FROM `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$table}_perms` WHERE _type = 'write'")->execute(); + $this->pdo->prepare("DELETE FROM `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$table}_perms` WHERE _type = 'write'")->execute(); } catch (\Throwable $th) { Console::warning("Remove 'write' permissions from {$table}: {$th->getMessage()}"); } @@ -311,7 +311,7 @@ class V15 extends Migration */ protected function getSQLColumnTypes(string $table): array { - $query = $this->pdo->prepare("SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '_{$this->project->getInternalId()}_{$table}' AND table_schema = '{$this->projectDB->getDefaultDatabase()}'"); + $query = $this->pdo->prepare("SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '_{$this->project->getInternalId()}_{$table}' AND table_schema = '{$this->projectDB->getDatabase()}'"); $query->execute(); return array_reduce($query->fetchAll(), function (array $carry, array $item) { @@ -333,8 +333,8 @@ class V15 extends Migration if ($columns[$attribute] === 'int') { try { - $this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$table}` MODIFY {$attribute} VARCHAR(64)")->execute(); - $this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$table}` SET {$attribute} = IF({$attribute} = 0, NULL, FROM_UNIXTIME({$attribute}))")->execute(); + $this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$table}` MODIFY {$attribute} VARCHAR(64)")->execute(); + $this->pdo->prepare("UPDATE `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$table}` SET {$attribute} = IF({$attribute} = 0, NULL, FROM_UNIXTIME({$attribute}))")->execute(); $columns[$attribute] = 'varchar'; } catch (\Throwable $th) { Console::warning($th->getMessage()); @@ -343,7 +343,7 @@ class V15 extends Migration if ($columns[$attribute] === 'varchar') { try { - $this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$table}` MODIFY {$attribute} DATETIME(3)")->execute(); + $this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$table}` MODIFY {$attribute} DATETIME(3)")->execute(); } catch (\Throwable $th) { Console::warning($th->getMessage()); } @@ -372,7 +372,7 @@ class V15 extends Migration } } - $this->projectDB->deleteCachedCollection($table); + $this->projectDB->purgeCachedCollection($table); } /** @@ -389,7 +389,7 @@ class V15 extends Migration if (!array_key_exists('_permissions', $columns)) { try { - $this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$table}` ADD `_permissions` MEDIUMTEXT DEFAULT NULL")->execute(); + $this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$table}` ADD `_permissions` MEDIUMTEXT DEFAULT NULL")->execute(); } catch (\Throwable $th) { Console::warning("Add '_permissions' column to '{$table}': {$th->getMessage()}"); } @@ -410,7 +410,7 @@ class V15 extends Migration { $table ??= $document->getCollection(); - $query = $this->pdo->prepare("SELECT * FROM `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$table}_perms` WHERE _document = '{$document->getId()}'"); + $query = $this->pdo->prepare("SELECT * FROM `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_{$table}_perms` WHERE _document = '{$document->getId()}'"); $query->execute(); $results = $query->fetchAll(); $permissions = []; @@ -479,7 +479,7 @@ class V15 extends Migration $this->createCollection('cache'); Console::log('Created new Collection "variables" collection'); $this->createCollection('variables'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'abuse': @@ -1472,9 +1472,9 @@ class V15 extends Migration $from = $this->pdo->quote($from); $to = $this->pdo->quote($to); - $this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_stats` SET metric = {$to} WHERE metric = {$from}")->execute(); + $this->pdo->prepare("UPDATE `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_stats` SET metric = {$to} WHERE metric = {$from}")->execute(); } catch (\Throwable $th) { - Console::warning("Migrating steps from {$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_stats:" . $th->getMessage()); + Console::warning("Migrating steps from {$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}_stats:" . $th->getMessage()); } } diff --git a/src/Appwrite/Migration/Version/V17.php b/src/Appwrite/Migration/Version/V17.php index c0e2498e7f..4bdae76621 100644 --- a/src/Appwrite/Migration/Version/V17.php +++ b/src/Appwrite/Migration/Version/V17.php @@ -48,7 +48,7 @@ class V17 extends Migration try { $this->projectDB->updateAttribute($id, 'mimeType', Database::VAR_STRING, 255, true, false); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'mimeType' from {$id}: {$th->getMessage()}"); } @@ -76,7 +76,7 @@ class V17 extends Migration * Create 'size' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'size'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'size' from {$id}: {$th->getMessage()}"); } @@ -88,7 +88,7 @@ class V17 extends Migration * Update 'mimeType' attribute size (127->255) */ $this->projectDB->updateAttribute($id, 'mimeType', Database::VAR_STRING, 255, true, false); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'mimeType' from {$id}: {$th->getMessage()}"); } @@ -98,7 +98,7 @@ class V17 extends Migration * Create 'bucketInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'bucketInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'deploymentInternalId' from {$id}: {$th->getMessage()}"); } @@ -110,7 +110,7 @@ class V17 extends Migration * Delete 'endTime' attribute (use startTime+duration if needed) */ $this->projectDB->deleteAttribute($id, 'endTime'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'endTime' from {$id}: {$th->getMessage()}"); } @@ -120,7 +120,7 @@ class V17 extends Migration * Rename 'outputPath' to 'path' */ $this->projectDB->renameAttribute($id, 'outputPath', 'path'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'path' from {$id}: {$th->getMessage()}"); } @@ -130,7 +130,7 @@ class V17 extends Migration * Create 'deploymentInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'deploymentInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'deploymentInternalId' from {$id}: {$th->getMessage()}"); } @@ -142,7 +142,7 @@ class V17 extends Migration * Delete 'type' attribute */ $this->projectDB->deleteAttribute($id, 'type'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'type' from {$id}: {$th->getMessage()}"); } @@ -154,7 +154,7 @@ class V17 extends Migration * Create 'resourceInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'resourceInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'resourceInternalId' from {$id}: {$th->getMessage()}"); } @@ -166,7 +166,7 @@ class V17 extends Migration * Create 'deploymentInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'deploymentInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'deploymentInternalId' from {$id}: {$th->getMessage()}"); } @@ -176,7 +176,7 @@ class V17 extends Migration * Create 'scheduleInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); } @@ -186,7 +186,7 @@ class V17 extends Migration * Delete 'scheduleUpdatedAt' attribute */ $this->projectDB->deleteAttribute($id, 'scheduleUpdatedAt'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'scheduleUpdatedAt' from {$id}: {$th->getMessage()}"); } @@ -198,7 +198,7 @@ class V17 extends Migration * Create 'resourceInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'resourceInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'resourceInternalId' from {$id}: {$th->getMessage()}"); } @@ -208,7 +208,7 @@ class V17 extends Migration * Create 'buildInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'buildInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'buildInternalId' from {$id}: {$th->getMessage()}"); } @@ -220,7 +220,7 @@ class V17 extends Migration * Create 'functionInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'functionInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'functionInternalId' from {$id}: {$th->getMessage()}"); } @@ -230,7 +230,7 @@ class V17 extends Migration * Create 'deploymentInternalId' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'deploymentInternalId'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'deploymentInternalId' from {$id}: {$th->getMessage()}"); } diff --git a/src/Appwrite/Migration/Version/V18.php b/src/Appwrite/Migration/Version/V18.php index 839269f940..ac4093aaca 100644 --- a/src/Appwrite/Migration/Version/V18.php +++ b/src/Appwrite/Migration/Version/V18.php @@ -106,7 +106,7 @@ class V18 extends Migration * Create 'passwordHistory' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'passwordHistory' from {$id}: {$th->getMessage()}"); } @@ -117,7 +117,7 @@ class V18 extends Migration * Create 'prefs' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'prefs'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'prefs' from {$id}: {$th->getMessage()}"); } @@ -128,7 +128,7 @@ class V18 extends Migration * Create 'options' attribute */ $this->createAttributeFromCollection($this->projectDB, $id, 'options'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'options' from {$id}: {$th->getMessage()}"); } @@ -244,7 +244,7 @@ class V18 extends Migration /** * Create 'documentSecurity' column */ - $this->pdo->prepare("ALTER TABLE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata` ADD COLUMN IF NOT EXISTS documentSecurity TINYINT(1);")->execute(); + $this->pdo->prepare("ALTER TABLE `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}__metadata` ADD COLUMN IF NOT EXISTS documentSecurity TINYINT(1);")->execute(); } catch (\Throwable $th) { Console::warning($th->getMessage()); } @@ -253,7 +253,7 @@ class V18 extends Migration /** * Set 'documentSecurity' column to 1 if NULL */ - $this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata` SET documentSecurity = 1 WHERE documentSecurity IS NULL")->execute(); + $this->pdo->prepare("UPDATE `{$this->projectDB->getDatabase()}`.`_{$this->project->getInternalId()}__metadata` SET documentSecurity = 1 WHERE documentSecurity IS NULL")->execute(); } catch (\Throwable $th) { Console::warning($th->getMessage()); } diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 46a0a073a6..a92fb0a5fb 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -56,7 +56,7 @@ class V19 extends Migration protected function migrateDomains(): void { - if ($this->consoleDB->exists($this->consoleDB->getDefaultDatabase(), 'domains')) { + if ($this->consoleDB->exists($this->consoleDB->getDatabase(), 'domains')) { foreach ($this->documentsIterator('domains') as $domain) { $status = 'created'; if ($domain->getAttribute('verification', false)) { @@ -106,7 +106,7 @@ class V19 extends Migration try { $this->createAttributeFromCollection($this->projectDB, $id, 'bucketInternalId', 'files'); - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); } catch (\Throwable $th) { Console::warning("'bucketInternalId' from {$id}: {$th->getMessage()}"); } @@ -166,7 +166,7 @@ class V19 extends Migration Console::warning("'error' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'buckets': @@ -194,7 +194,7 @@ class V19 extends Migration } } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'builds': @@ -217,7 +217,7 @@ class V19 extends Migration Console::warning("'path' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'certificates': @@ -233,7 +233,7 @@ class V19 extends Migration Console::warning("'logs' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'databases': @@ -243,7 +243,7 @@ class V19 extends Migration Console::warning("'enabled' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'deployments': @@ -306,7 +306,7 @@ class V19 extends Migration } } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'executions': @@ -367,7 +367,7 @@ class V19 extends Migration Console::warning("'_key_responseStatusCode' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'files': @@ -394,7 +394,7 @@ class V19 extends Migration } } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'functions': @@ -456,7 +456,7 @@ class V19 extends Migration } } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'memberships': @@ -466,7 +466,7 @@ class V19 extends Migration Console::warning("'teamInternalId' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); // Intentional fall through to update memberships.userInternalId case 'sessions': @@ -477,7 +477,7 @@ class V19 extends Migration Console::warning("'userInternalId' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'domains': @@ -490,7 +490,7 @@ class V19 extends Migration Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'projects': @@ -508,7 +508,7 @@ class V19 extends Migration } } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'stats': @@ -521,26 +521,26 @@ class V19 extends Migration // Holding off on these until a future release // try { // $this->projectDB->deleteAttribute($id, 'type'); - // $this->projectDB->deleteCachedCollection($id); + // $this->projectDB->purgeCachedCollection($id); // } catch (\Throwable $th) { // Console::warning("'type' from {$id}: {$th->getMessage()}"); // } // try { // $this->projectDB->deleteIndex($id, '_key_metric_period_time'); - // $this->projectDB->deleteCachedCollection($id); + // $this->projectDB->purgeCachedCollection($id); // } catch (\Throwable $th) { // Console::warning("'_key_metric_period_time' from {$id}: {$th->getMessage()}"); // } // try { // $this->createIndexFromCollection($this->projectDB, $id, '_key_metric_period_time'); - // $this->projectDB->deleteCachedCollection($id); + // $this->projectDB->purgeCachedCollection($id); // } catch (\Throwable $th) { // Console::warning("'_key_metric_period_time' from {$id}: {$th->getMessage()}"); // } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'users': @@ -568,7 +568,7 @@ class V19 extends Migration Console::warning("'_key_accessedAt' from {$id}: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; case 'variables': @@ -616,7 +616,7 @@ class V19 extends Migration } } - $this->projectDB->deleteCachedCollection($id); + $this->projectDB->purgeCachedCollection($id); break; default: @@ -775,7 +775,7 @@ class V19 extends Migration Console::warning("'domains' from projects: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection('projects'); + $this->projectDB->purgeCachedCollection('projects'); try { $this->projectDB->deleteAttribute('builds', 'stderr'); @@ -789,7 +789,7 @@ class V19 extends Migration Console::warning("'stdout' from builds: {$th->getMessage()}"); } - $this->projectDB->deleteCachedCollection('builds'); + $this->projectDB->purgeCachedCollection('builds'); } /** diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php index 757b29c1b6..9b27176876 100644 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php @@ -90,12 +90,12 @@ class DeleteOrphanedProjects extends Action ->getResource(); $dbForProject = new Database($adapter, $cache); - $dbForProject->setDefaultDatabase('appwrite'); + $dbForProject->setDatabase('appwrite'); $dbForProject->setNamespace('_' . $project->getInternalId()); $collectionsCreated = 0; $cnt++; - if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) { + if ($dbForProject->exists($dbForProject->getDatabase(), Database::METADATA)) { $collectionsCreated = $dbForProject->count(Database::METADATA); } @@ -113,7 +113,7 @@ class DeleteOrphanedProjects extends Action foreach ($collections as $collection) { if ($commit) { $dbForProject->deleteCollection($collection->getId()); - $dbForConsole->deleteCachedCollection($collection->getId()); + $dbForConsole->purgeCachedCollection($collection->getId()); } Console::info('--Deleting collection (' . $collection->getId() . ') project no (' . $project->getInternalId() . ')'); } @@ -121,12 +121,12 @@ class DeleteOrphanedProjects extends Action if ($commit) { $dbForConsole->deleteDocument('projects', $project->getId()); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) { try { $dbForProject->deleteCollection(Database::METADATA); - $dbForProject->deleteCachedCollection(Database::METADATA); + $dbForProject->purgeCachedCollection(Database::METADATA); } catch (\Throwable $th) { Console::warning('Metadata collection does not exist'); } diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php index 2980f13375..e46de1b522 100644 --- a/src/Appwrite/Platform/Tasks/Maintenance.php +++ b/src/Appwrite/Platform/Tasks/Maintenance.php @@ -59,6 +59,7 @@ class Maintenance extends Action $this->renewCertificates($dbForConsole, $queueForCertificates); $this->notifyDeleteCache($cacheRetention, $queueForDeletes); $this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes); + $this->notifyDeleteTargets($queueForDeletes); }, $interval); } @@ -161,8 +162,7 @@ class Maintenance extends Action private function notifyDeleteCache($interval, Delete $queueForDeletes): void { - - ($queueForDeletes) + $queueForDeletes ->setType(DELETE_TYPE_CACHE_BY_TIMESTAMP) ->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval)) ->trigger(); @@ -170,10 +170,16 @@ class Maintenance extends Action private function notifyDeleteSchedules($interval, Delete $queueForDeletes): void { - - ($queueForDeletes) + $queueForDeletes ->setType(DELETE_TYPE_SCHEDULES) ->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval)) ->trigger(); } + + private function notifyDeleteTargets(Delete $queueForDeletes): void + { + $queueForDeletes + ->setType(DELETE_TYPE_EXPIRED_TARGETS) + ->trigger(); + } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 1ec8e471bb..f0aa559a7f 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -73,6 +73,7 @@ abstract class ScheduleBase extends Action '$id' => $schedule->getId(), 'resourceId' => $schedule->getAttribute('resourceId'), 'schedule' => $schedule->getAttribute('schedule'), + 'active' => $schedule->getAttribute('active'), 'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'), 'project' => $project, // TODO: @Meldiron Send only ID to worker to reduce memory usage here 'resource' => $resource, // TODO: @Meldiron Send only ID to worker to reduce memory usage here diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index 19fc426637..cc641b434a 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -33,6 +33,10 @@ class ScheduleMessages extends ScheduleBase protected function enqueueResources(Group $pools, Database $dbForConsole): void { foreach ($this->schedules as $schedule) { + if (!$schedule['active']) { + continue; + } + $now = DateTime::now(); $scheduledAt = DateTime::formatTz($schedule['schedule']); @@ -44,24 +48,17 @@ class ScheduleMessages extends ScheduleBase $queue = $pools->get('queue')->pop(); $connection = $queue->getResource(); $queueForMessaging = new Messaging($connection); - $queueForDeletes = new Delete($connection); $queueForMessaging ->setMessageId($schedule['resourceId']) ->setProject($schedule['project']) ->trigger(); - $dbForConsole->updateDocument( + $dbForConsole->deleteDocument( 'schedules', $schedule['$id'], - new Document(['active' => false]) ); - $queueForDeletes - ->setType(DELETE_TYPE_SCHEDULES) - ->setDocument($schedule) - ->trigger(); - $queue->reclaim(); unset($this->schedules[$schedule['resourceId']]); diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php index 4ef939d900..0b670cbe50 100644 --- a/src/Appwrite/Platform/Workers/Databases.php +++ b/src/Appwrite/Platform/Workers/Databases.php @@ -195,10 +195,10 @@ class Databases extends Action } if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) { - $dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); } - $dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); } /** @@ -346,12 +346,12 @@ class Databases extends Action } } - $dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId); - $dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); + $dbForProject->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); if (!$relatedCollection->isEmpty() && !$relatedAttribute->isEmpty()) { - $dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); - $dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); + $dbForProject->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); } } @@ -413,7 +413,7 @@ class Databases extends Action $this->trigger($database, $collection, $index, $project, $projectId, $events); } - $dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); } /** @@ -471,7 +471,7 @@ class Databases extends Action $this->trigger($database, $collection, $index, $project, $projectId, $events); } - $dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collection->getId()); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); } /** @@ -526,8 +526,8 @@ class Databases extends Action } $relatedCollection = $dbForProject->getDocument('database_' . $databaseInternalId, $relationship['relatedCollection']); $dbForProject->deleteDocument('attributes', $databaseInternalId . '_' . $relatedCollection->getInternalId() . '_' . $relationship['twoWayKey']); - $dbForProject->deleteCachedDocument('database_' . $databaseInternalId, $relatedCollection->getId()); - $dbForProject->deleteCachedCollection('database_' . $databaseInternalId . '_collection_' . $relatedCollection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $databaseInternalId, $relatedCollection->getId()); + $dbForProject->purgeCachedCollection('database_' . $databaseInternalId . '_collection_' . $relatedCollection->getInternalId()); } $dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId()); diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 581fee0add..dd1949f845 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -122,11 +122,9 @@ class Deletes extends Action break; } break; - case DELETE_TYPE_EXECUTIONS: $this->deleteExecutionLogs($project, $getProjectDB, $executionRetention); break; - case DELETE_TYPE_AUDIT: if (!$project->isEmpty()) { $this->deleteAuditLogs($project, $getProjectDB, $auditRetention); @@ -139,11 +137,9 @@ class Deletes extends Action case DELETE_TYPE_ABUSE: $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention); break; - case DELETE_TYPE_REALTIME: $this->deleteRealtimeUsage($dbForConsole, $datetime); break; - case DELETE_TYPE_SESSIONS: $this->deleteExpiredSessions($project, $getProjectDB); break; @@ -157,17 +153,19 @@ class Deletes extends Action $this->deleteCacheByDate($project, $getProjectDB, $datetime); break; case DELETE_TYPE_SCHEDULES: - $this->deleteSchedules($dbForConsole, $getProjectDB, $datetime, $document); + $this->deleteSchedules($dbForConsole, $getProjectDB, $datetime); break; case DELETE_TYPE_TOPIC: $this->deleteTopic($project, $getProjectDB, $document); break; case DELETE_TYPE_TARGET: - $this->deleteTarget($project, $getProjectDB, $document); + $this->deleteTargetSubscribers($project, $getProjectDB, $document); + break; + case DELETE_TYPE_EXPIRED_TARGETS: + $this->deleteExpiredTargets($project, $getProjectDB); break; default: throw new \Exception('No delete operation for type: ' . \strval($type)); - break; } } @@ -183,13 +181,12 @@ class Deletes extends Action * @throws Structure * @throws DatabaseException */ - private function deleteSchedules(Database $dbForConsole, callable $getProjectDB, string $datetime, ?Document $document = null): void + private function deleteSchedules(Database $dbForConsole, callable $getProjectDB, string $datetime): void { $this->listByGroup( 'schedules', [ Query::equal('region', [App::getEnv('_APP_REGION', 'default')]), - Query::equal('resourceType', [$document->getAttribute('resourceType')]), Query::lessThanEqual('resourceUpdatedAt', $datetime), Query::equal('active', [false]), ], @@ -230,17 +227,20 @@ class Deletes extends Action * @param Document $topic * @throws Exception */ - protected function deleteTopic(Document $project, callable $getProjectDB, Document $topic) + private function deleteTopic(Document $project, callable $getProjectDB, Document $topic) { if ($topic->isEmpty()) { Console::error('Failed to delete subscribers. Topic not found'); return; } - $dbForProject = $getProjectDB($project); - $this->deleteByGroup('subscribers', [ - Query::equal('topicInternalId', [$topic->getInternalId()]) - ], $dbForProject); + $this->deleteByGroup( + 'subscribers', + [ + Query::equal('topicInternalId', [$topic->getInternalId()]) + ], + $getProjectDB($project) + ); } /** @@ -249,7 +249,7 @@ class Deletes extends Action * @param Document $target * @throws Exception */ - protected function deleteTarget(Document $project, callable $getProjectDB, Document $target) + private function deleteTargetSubscribers(Document $project, callable $getProjectDB, Document $target) { /** @var Database */ $dbForProject = $getProjectDB($project); @@ -272,6 +272,27 @@ class Deletes extends Action ); } + /** + * @param Document $project + * @param callable $getProjectDB + * @param Document $target + * @return void + * @throws Exception + */ + private function deleteExpiredTargets(Document $project, callable $getProjectDB) + { + $this->deleteByGroup( + 'targets', + [ + Query::equal('expired', [true]) + ], + $getProjectDB($project), + function (Document $target) use ($getProjectDB, $project) { + $this->deleteTargetSubscribers($project, $getProjectDB, $target); + } + ); + } + /** * @param Document $project * @param callable $getProjectDB @@ -390,8 +411,8 @@ class Deletes extends Action } $relatedCollection = $dbForProject->getDocument('database_' . $databaseInternalId, $relationship['relatedCollection']); $dbForProject->deleteDocument('attributes', $databaseInternalId . '_' . $relatedCollection->getInternalId() . '_' . $relationship['twoWayKey']); - $dbForProject->deleteCachedDocument('database_' . $databaseInternalId, $relatedCollection->getId()); - $dbForProject->deleteCachedCollection('database_' . $databaseInternalId . '_collection_' . $relatedCollection->getInternalId()); + $dbForProject->purgeCachedDocument('database_' . $databaseInternalId, $relatedCollection->getId()); + $dbForProject->purgeCachedCollection('database_' . $databaseInternalId . '_collection_' . $relatedCollection->getInternalId()); } $dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $document->getInternalId()); @@ -447,7 +468,7 @@ class Deletes extends Action $dbForProject, function (Document $membership) use ($dbForProject) { $userId = $membership->getAttribute('userId'); - $dbForProject->deleteCachedDocument('users', $userId); + $dbForProject->purgeCachedDocument('users', $userId); } ); } @@ -582,7 +603,7 @@ class Deletes extends Action Query::equal('userInternalId', [$userInternalId]) ], $dbForProject); - $dbForProject->deleteCachedDocument('users', $userId); + $dbForProject->purgeCachedDocument('users', $userId); // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ @@ -613,14 +634,14 @@ class Deletes extends Action ], $dbForProject); // Delete targets - $this->listByGroup( + $this->deleteByGroup( 'targets', [ Query::equal('userInternalId', [$userInternalId]) ], $dbForProject, function (Document $target) use ($getProjectDB, $project) { - $this->deleteTarget($project, $getProjectDB, $target); + $this->deleteTargetSubscribers($project, $getProjectDB, $target); } ); } diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index e55ef12bd4..f1a0bea6e6 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -18,6 +18,7 @@ use Utopia\Database\Query; use Utopia\Messaging\Adapter\Email as EmailAdapter; use Utopia\Messaging\Adapter\Email\Mailgun; use Utopia\Messaging\Adapter\Email\Sendgrid; +use Utopia\Messaging\Adapter\Email\SMTP; use Utopia\Messaging\Adapter\Push as PushAdapter; use Utopia\Messaging\Adapter\Push\APNS; use Utopia\Messaging\Adapter\Push\FCM; @@ -216,8 +217,8 @@ class Messaging extends Action $batches = \array_chunk($identifiers, $maxBatchSize); $batchIndex = 0; - return batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex, $dbForProject) { - return function () use ($batch, $message, $provider, $adapter, $batchIndex, $dbForProject) { + return batch(\array_map(function ($batch) use ($message, $provider, $adapter, &$batchIndex, $dbForProject) { + return function () use ($batch, $message, $provider, $adapter, &$batchIndex, $dbForProject) { $deliveredTotal = 0; $deliveryErrors = []; $messageData = clone $message; @@ -248,7 +249,11 @@ class Messaging extends Action ]); if ($target instanceof Document && !$target->isEmpty()) { - $dbForProject->deleteDocument('targets', $target->getId()); + $dbForProject->updateDocument( + 'targets', + $target->getId(), + $target->setAttribute('expired', true) + ); } } } @@ -403,10 +408,24 @@ class Messaging extends Action private function email(Document $provider): ?EmailAdapter { - $credentials = $provider->getAttribute('credentials'); + $credentials = $provider->getAttribute('credentials', []); + $options = $provider->getAttribute('options', []); return match ($provider->getAttribute('provider')) { 'mock' => new Mock('username', 'password'), - 'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']), + 'smtp' => new SMTP( + $credentials['host'], + $credentials['port'], + $credentials['username'], + $credentials['password'], + $options['encryption'], + $options['autoTLS'], + $options['mailer'], + ), + 'mailgun' => new Mailgun( + $credentials['apiKey'], + $credentials['domain'], + $credentials['isEuRegion'] + ), 'sendgrid' => new Sendgrid($credentials['apiKey']), default => null }; @@ -414,16 +433,10 @@ class Messaging extends Action private function buildEmailMessage(Database $dbForProject, Document $message, Document $provider): Email { - $fromName = $provider['options']['fromName']; - $fromEmail = $provider['options']['fromEmail']; - $replyToEmail = null; - $replyToName = null; - - if (isset($provider['options']['replyToName']) && isset($provider['options']['replyToEmail'])) { - $replyToName = $provider['options']['replyToName']; - $replyToEmail = $provider['options']['replyToEmail']; - } - + $fromName = $provider['options']['fromName'] ?? null; + $fromEmail = $provider['options']['fromEmail'] ?? null; + $replyToEmail = $provider['options']['replyToEmail'] ?? null; + $replyToName = $provider['options']['replyToName'] ?? null; $data = $message['data'] ?? []; $ccTargets = $data['cc'] ?? []; $bccTargets = $data['bcc'] ?? []; diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 95c31d07ad..d2a126f22b 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -225,7 +225,7 @@ class Migrations extends Action ]); $this->dbForConsole->createDocument('keys', $key); - $this->dbForConsole->deleteCachedDocument('projects', $project->getId()); + $this->dbForConsole->purgeCachedDocument('projects', $project->getId()); return $key; } diff --git a/src/Appwrite/Platform/Workers/Webhooks.php b/src/Appwrite/Platform/Workers/Webhooks.php index 880ae587e1..6f7ab1c698 100644 --- a/src/Appwrite/Platform/Workers/Webhooks.php +++ b/src/Appwrite/Platform/Workers/Webhooks.php @@ -163,13 +163,13 @@ class Webhooks extends Action } $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); $this->errors[] = $logs; } else { $webhook->setAttribute('attempts', 0); // Reset attempts on success $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); + $dbForConsole->purgeCachedDocument('projects', $project->getId()); } } diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 28a259410c..6683c63d23 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -5,6 +5,7 @@ namespace Tests\E2E\Services\Account; use Tests\E2E\Client; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Query; trait AccountBase { diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index c6c0b5cb7d..0b3ac5edc6 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -9,6 +9,7 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\SideClient; use Utopia\Database\DateTime; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; use function sleep; @@ -344,7 +345,9 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'queries' => [ 'limit(1)' ], + 'queries' => [ + Query::limit(1)->toString() + ] ]); $this->assertEquals($responseLimit['headers']['status-code'], 200); @@ -361,7 +364,9 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'queries' => [ 'offset(1)' ], + 'queries' => [ + Query::offset(1)->toString() + ] ]); $this->assertEquals($responseOffset['headers']['status-code'], 200); @@ -378,7 +383,10 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'queries' => [ 'limit(1)', 'offset(1)' ], + 'queries' => [ + Query::offset(1)->toString(), + Query::limit(1)->toString() + ] ]); $this->assertEquals($responseLimitOffset['headers']['status-code'], 200); diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 4d5eb4b803..7580d73711 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -6,13 +6,152 @@ use Appwrite\Extend\Exception; use Tests\E2E\Client; use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Validator\JSON; trait DatabasesBase { + /** + * @throws \Utopia\Database\Exception + * @throws \Utopia\Database\Exception\Query + */ + public function testOrQueries(): void + { + // Create database + $database = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Or queries' + ]); + + $this->assertNotEmpty($database['body']['$id']); + $this->assertEquals(201, $database['headers']['status-code']); + $this->assertEquals('Or queries', $database['body']['name']); + + $databaseId = $database['body']['$id']; + + // Create Collection + $presidents = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'USA Presidents', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + ], + ]); + + $this->assertEquals(201, $presidents['headers']['status-code']); + $this->assertEquals($presidents['body']['name'], 'USA Presidents'); + + // Create Attributes + $firstName = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $presidents['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'first_name', + 'size' => 256, + 'required' => true, + ]); + $this->assertEquals(202, $firstName['headers']['status-code']); + + $lastName = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $presidents['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'last_name', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals(202, $lastName['headers']['status-code']); + + // Wait for worker + sleep(2); + + $document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $presidents['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'first_name' => 'Donald', + 'last_name' => 'Trump', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $document1['headers']['status-code']); + + $document2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $presidents['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'first_name' => 'George', + 'last_name' => 'Bush', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + $this->assertEquals(201, $document2['headers']['status-code']); + + $document3 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $presidents['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'first_name' => 'Joe', + 'last_name' => 'Biden', + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals(201, $document3['headers']['status-code']); + + $documents = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/collections/' . $presidents['body']['$id'] . '/documents', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::select(['first_name', 'last_name'])->toString(), + Query::or([ + Query::equal('first_name', ['Donald']), + Query::equal('last_name', ['Bush']) + ])->toString(), + Query::limit(999)->toString(), + Query::offset(0)->toString() + ], + ] + ); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertCount(2, $documents['body']['documents']); + } + public function testCreateDatabase(): array { /** @@ -243,6 +382,18 @@ trait DatabasesBase 'twoWayKey' => 'movie' ]); + $integers = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/attributes/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'integers', + 'required' => false, + 'array' => true, + 'min' => 10, + 'max' => 99, + ]); + $this->assertEquals(202, $title['headers']['status-code']); $this->assertEquals($title['body']['key'], 'title'); $this->assertEquals($title['body']['type'], 'string'); @@ -293,6 +444,13 @@ trait DatabasesBase $this->assertEquals($relationship['body']['twoWay'], true); $this->assertEquals($relationship['body']['twoWayKey'], 'movie'); + $this->assertEquals(202, $integers['headers']['status-code']); + $this->assertEquals($integers['body']['key'], 'integers'); + $this->assertEquals($integers['body']['type'], 'integer'); + $this->assertArrayNotHasKey('size', $integers['body']); + $this->assertEquals($integers['body']['required'], false); + $this->assertEquals($integers['body']['array'], true); + // wait for database worker to create attributes sleep(2); @@ -303,7 +461,7 @@ trait DatabasesBase ])); $this->assertIsArray($movies['body']['attributes']); - $this->assertCount(8, $movies['body']['attributes']); + $this->assertCount(9, $movies['body']['attributes']); $this->assertEquals($movies['body']['attributes'][0]['key'], $title['body']['key']); $this->assertEquals($movies['body']['attributes'][1]['key'], $description['body']['key']); $this->assertEquals($movies['body']['attributes'][2]['key'], $tagline['body']['key']); @@ -312,6 +470,7 @@ trait DatabasesBase $this->assertEquals($movies['body']['attributes'][5]['key'], $actors['body']['key']); $this->assertEquals($movies['body']['attributes'][6]['key'], $datetime['body']['key']); $this->assertEquals($movies['body']['attributes'][7]['key'], $relationship['body']['key']); + $this->assertEquals($movies['body']['attributes'][8]['key'], $integers['body']['key']); return $data; } @@ -327,7 +486,11 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ - 'queries' => ['equal("type", "string")', 'limit(2)', 'cursorAfter(title)'], + 'queries' => [ + Query::equal('type', ['string'])->toString(), + Query::limit(2)->toString(), + Query::cursorAfter(new Document(['$id' => 'title']))->toString() + ], ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(2, \count($response['body']['attributes'])); @@ -336,7 +499,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ - 'queries' => ['select(["key"])'], + 'queries' => [Query::select(['key'])->toString()], ]); $this->assertEquals(Exception::GENERAL_ARGUMENT_INVALID, $response['body']['type']); $this->assertEquals(400, $response['headers']['status-code']); @@ -531,6 +694,29 @@ trait DatabasesBase 'twoWayKey' => 'twoWayKey' ]); + $strings = $this->client->call(Client::METHOD_POST, $attributesPath . '/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'names', + 'size' => 512, + 'required' => false, + 'array' => true, + ]); + + $integers = $this->client->call(Client::METHOD_POST, $attributesPath . '/integer', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'numbers', + 'required' => false, + 'array' => true, + 'min' => 1, + 'max' => 999, + ]); + $this->assertEquals(202, $string['headers']['status-code']); $this->assertEquals('string', $string['body']['key']); $this->assertEquals('string', $string['body']['type']); @@ -615,6 +801,22 @@ trait DatabasesBase $this->assertEquals(true, $relationship['body']['twoWay']); $this->assertEquals('twoWayKey', $relationship['body']['twoWayKey']); + $this->assertEquals(202, $strings['headers']['status-code']); + $this->assertEquals('names', $strings['body']['key']); + $this->assertEquals('string', $strings['body']['type']); + $this->assertEquals(false, $strings['body']['required']); + $this->assertEquals(true, $strings['body']['array']); + $this->assertEquals(null, $strings['body']['default']); + + $this->assertEquals(202, $integers['headers']['status-code']); + $this->assertEquals('numbers', $integers['body']['key']); + $this->assertEquals('integer', $integers['body']['type']); + $this->assertEquals(false, $integers['body']['required']); + $this->assertEquals(true, $integers['body']['array']); + $this->assertEquals(1, $integers['body']['min']); + $this->assertEquals(999, $integers['body']['max']); + $this->assertEquals(null, $integers['body']['default']); + // Wait for database worker to create attributes sleep(5); @@ -678,6 +880,18 @@ trait DatabasesBase 'x-appwrite-key' => $this->getProject()['apiKey'] ])); + $stringsResponse = $this->client->call(Client::METHOD_GET, $attributesPath . '/' . $strings['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $integersResponse = $this->client->call(Client::METHOD_GET, $attributesPath . '/' . $integers['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + $this->assertEquals(200, $stringResponse['headers']['status-code']); $this->assertEquals($string['body']['key'], $stringResponse['body']['key']); $this->assertEquals($string['body']['type'], $stringResponse['body']['type']); @@ -778,11 +992,11 @@ trait DatabasesBase ])); $this->assertEquals(200, $attributes['headers']['status-code']); - $this->assertEquals(10, $attributes['body']['total']); + $this->assertEquals(12, $attributes['body']['total']); $attributes = $attributes['body']['attributes']; $this->assertIsArray($attributes); - $this->assertCount(10, $attributes); + $this->assertCount(12, $attributes); $this->assertEquals($stringResponse['body']['key'], $attributes[0]['key']); $this->assertEquals($stringResponse['body']['type'], $attributes[0]['type']); @@ -867,6 +1081,22 @@ trait DatabasesBase $this->assertEquals($relationshipResponse['body']['twoWay'], $attributes[9]['twoWay']); $this->assertEquals($relationshipResponse['body']['twoWayKey'], $attributes[9]['twoWayKey']); + $this->assertEquals($stringsResponse['body']['key'], $attributes[10]['key']); + $this->assertEquals($stringsResponse['body']['type'], $attributes[10]['type']); + $this->assertEquals($stringsResponse['body']['status'], $attributes[10]['status']); + $this->assertEquals($stringsResponse['body']['required'], $attributes[10]['required']); + $this->assertEquals($stringsResponse['body']['array'], $attributes[10]['array']); + $this->assertEquals($stringsResponse['body']['default'], $attributes[10]['default']); + + $this->assertEquals($integersResponse['body']['key'], $attributes[11]['key']); + $this->assertEquals($integersResponse['body']['type'], $attributes[11]['type']); + $this->assertEquals($integersResponse['body']['status'], $attributes[11]['status']); + $this->assertEquals($integersResponse['body']['required'], $attributes[11]['required']); + $this->assertEquals($integersResponse['body']['array'], $attributes[11]['array']); + $this->assertEquals($integersResponse['body']['default'], $attributes[11]['default']); + $this->assertEquals($integersResponse['body']['min'], $attributes[11]['min']); + $this->assertEquals($integersResponse['body']['max'], $attributes[11]['max']); + $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -878,7 +1108,7 @@ trait DatabasesBase $attributes = $collection['body']['attributes']; $this->assertIsArray($attributes); - $this->assertCount(10, $attributes); + $this->assertCount(12, $attributes); $this->assertEquals($stringResponse['body']['key'], $attributes[0]['key']); $this->assertEquals($stringResponse['body']['type'], $attributes[0]['type']); @@ -963,6 +1193,22 @@ trait DatabasesBase $this->assertEquals($relationshipResponse['body']['twoWay'], $attributes[9]['twoWay']); $this->assertEquals($relationshipResponse['body']['twoWayKey'], $attributes[9]['twoWayKey']); + $this->assertEquals($stringsResponse['body']['key'], $attributes[10]['key']); + $this->assertEquals($stringsResponse['body']['type'], $attributes[10]['type']); + $this->assertEquals($stringsResponse['body']['status'], $attributes[10]['status']); + $this->assertEquals($stringsResponse['body']['required'], $attributes[10]['required']); + $this->assertEquals($stringsResponse['body']['array'], $attributes[10]['array']); + $this->assertEquals($stringsResponse['body']['default'], $attributes[10]['default']); + + $this->assertEquals($integersResponse['body']['key'], $attributes[11]['key']); + $this->assertEquals($integersResponse['body']['type'], $attributes[11]['type']); + $this->assertEquals($integersResponse['body']['status'], $attributes[11]['status']); + $this->assertEquals($integersResponse['body']['required'], $attributes[11]['required']); + $this->assertEquals($integersResponse['body']['array'], $attributes[11]['array']); + $this->assertEquals($integersResponse['body']['default'], $attributes[11]['default']); + $this->assertEquals($integersResponse['body']['min'], $attributes[11]['min']); + $this->assertEquals($integersResponse['body']['max'], $attributes[11]['max']); + /** * Test for FAILURE */ @@ -1127,6 +1373,56 @@ trait DatabasesBase $this->assertEquals(400, $tooLong['headers']['status-code']); $this->assertStringContainsString('Index length is longer than the maximum', $tooLong['body']['message']); + $fulltextArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'ft', + 'type' => 'fulltext', + 'attributes' => ['actors'], + ]); + + $this->assertEquals(400, $fulltextArray['headers']['status-code']); + $this->assertEquals('"Fulltext" index is forbidden on array attributes', $fulltextArray['body']['message']); + + $actorsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'index-actors', + 'type' => 'key', + 'attributes' => ['actors'], + ]); + + $this->assertEquals(202, $actorsArray['headers']['status-code']); + + $twoLevelsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'index-ip-actors', + 'type' => 'key', + 'attributes' => ['releaseYear', 'actors'], // 2 levels + ]); + + $this->assertEquals(202, $twoLevelsArray['headers']['status-code']); + + $unknown = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), [ + 'key' => 'index-unknown', + 'type' => 'key', + 'attributes' => ['Unknown'], + ]); + + $this->assertEquals(400, $unknown['headers']['status-code']); + $this->assertEquals('Unknown attribute: Unknown', $unknown['body']['message']); + return $data; } @@ -1141,7 +1437,10 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ - 'queries' => ['equal("type", "key")', 'limit(2)'], + 'queries' => [ + Query::equal('type', ['key'])->toString(), + Query::limit(2)->toString() + ], ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(2, \count($response['body']['indexes'])); @@ -1150,7 +1449,9 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]), [ - 'queries' => ['select(["key"])'], + 'queries' => [ + Query::select(['key'])->toString(), + ], ]); $this->assertEquals(Exception::GENERAL_ARGUMENT_INVALID, $response['body']['type']); $this->assertEquals(400, $response['headers']['status-code']); @@ -1196,7 +1497,8 @@ trait DatabasesBase 'Tom Holland', 'Zendaya Maree Stoermer', 'Samuel Jackson', - ] + ], + 'integers' => [50,60] ], 'permissions' => [ Permission::read(Role::user($this->getUser()['$id'])), @@ -1219,6 +1521,7 @@ trait DatabasesBase 'Tom Holland', 'Zendaya Maree Stoermer', ], + 'integers' => [50] ], 'permissions' => [ Permission::read(Role::user($this->getUser()['$id'])), @@ -1269,6 +1572,8 @@ trait DatabasesBase $this->assertEquals($document2['body']['actors'][1], 'Zendaya Maree Stoermer'); $this->assertEquals($document2['body']['actors'][2], 'Samuel Jackson'); $this->assertEquals($document2['body']['birthDay'], null); + $this->assertEquals($document2['body']['integers'][0], 50); + $this->assertEquals($document2['body']['integers'][1], 60); $this->assertEquals(201, $document3['headers']['status-code']); $this->assertEquals($data['moviesId'], $document3['body']['$collectionId']); @@ -1305,7 +1610,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("releaseYear")'], + 'queries' => [ + Query::orderAsc('releaseYear')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1327,7 +1634,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderDesc("releaseYear")'], + 'queries' => [ + Query::orderDesc('releaseYear')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1377,7 +1686,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'select(["title", "releaseYear", "$id"])', + Query::select(['title', 'releaseYear', '$id'])->toString(), ], ]); @@ -1411,7 +1720,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['documents'][0]['$id'] . '")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['documents'][0]['$id']]))->toString() + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1423,7 +1734,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['documents'][2]['$id'] . '")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['documents'][2]['$id']]))->toString() + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1436,7 +1749,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("releaseYear")'], + 'queries' => [ + Query::orderAsc('releaseYear')->toString() + ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1449,7 +1764,10 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['documents'][1]['$id'] . '")', 'orderAsc("releaseYear")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['documents'][1]['$id']]))->toString(), + Query::orderAsc('releaseYear')->toString() + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1463,7 +1781,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderDesc("releaseYear")'], + 'queries' => [ + Query::orderDesc('releaseYear')->toString() + ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1476,7 +1796,10 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['documents'][1]['$id'] . '")', 'orderDesc("releaseYear")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['documents'][1]['$id']]))->toString(), + Query::orderDesc('releaseYear')->toString() + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1490,7 +1813,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("unknown")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], ]); $this->assertEquals(400, $documents['headers']['status-code']); @@ -1522,7 +1847,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['documents'][2]['$id'] . '")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['documents'][2]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1534,7 +1861,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['documents'][0]['$id'] . '")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['documents'][0]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1547,7 +1876,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("releaseYear")'], + 'queries' => [ + Query::orderAsc('releaseYear')->toString(), + ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1560,7 +1891,10 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['documents'][1]['$id'] . '")', 'orderAsc("releaseYear")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['documents'][1]['$id']]))->toString(), + Query::orderAsc('releaseYear')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1574,7 +1908,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderDesc("releaseYear")'], + 'queries' => [ + Query::orderDesc('releaseYear')->toString(), + ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1587,7 +1923,10 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['documents'][1]['$id'] . '")', 'orderDesc("releaseYear")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['documents'][1]['$id']]))->toString(), + Query::orderDesc('releaseYear')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1607,7 +1946,10 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)', 'orderAsc("releaseYear")'], + 'queries' => [ + Query::orderAsc('releaseYear')->toString(), + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1618,7 +1960,11 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(2)', 'offset(1)', 'orderAsc("releaseYear")'], + 'queries' => [ + Query::orderAsc('releaseYear')->toString(), + Query::limit(2)->toString(), + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1639,7 +1985,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['search("title", "Captain America")'], + 'queries' => [ + Query::search('title', 'Captain America')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1650,7 +1998,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("$id", "' . $documents['body']['documents'][0]['$id'] . '")'], + 'queries' => [ + Query::equal('$id', [$documents['body']['documents'][0]['$id']])->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1661,7 +2011,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['search("title", "Homecoming")'], + 'queries' => [ + Query::search('title', 'Homecoming')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1672,7 +2024,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['search("title", "spider")'], + 'queries' => [ + Query::search('title', 'spider')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1684,7 +2038,21 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("releaseYear", 1944)'], + 'queries' => [ + Query::contains('title', ['spi'])->toString(), // like query + ], + ]); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(2, $documents['body']['total']); + + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('releaseYear', [1944])->toString(), + ], ]); $this->assertCount(1, $documents['body']['documents']); @@ -1694,7 +2062,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['notEqual("releaseYear", 1944)'], + 'queries' => [ + Query::notEqual('releaseYear', 1944)->toString(), + ], ]); $this->assertCount(2, $documents['body']['documents']); @@ -1705,7 +2075,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['greaterThan("$createdAt", "1976-06-12")'], + 'queries' => [ + Query::greaterThan('$createdAt', '1976-06-12')->toString(), + ], ]); $this->assertCount(3, $documents['body']['documents']); @@ -1714,7 +2086,9 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['lessThan("$createdAt", "1976-06-12")'], + 'queries' => [ + Query::lessThan('$createdAt', '1976-06-12')->toString(), + ], ]); $this->assertCount(0, $documents['body']['documents']); @@ -1723,15 +2097,45 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("actors", "Tom Holland")'], + 'queries' => [ + Query::contains('actors', ['Tom Holland', 'Samuel Jackson'])->toString(), + ], ]); + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(3, $documents['body']['total']); $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['greaterThan("birthDay", "1960-01-01 10:10:10+02:30")'], + 'queries' => [ + Query::contains('actors', ['Tom'])->toString(), // Full-match not like + ], + ]); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(0, $documents['body']['total']); + + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::greaterThan('birthDay', '16/01/2024 12:00:00AM')->toString(), + ], + ]); + + $this->assertEquals(400, $documents['headers']['status-code']); + $this->assertEquals('Invalid query: Query value is invalid for attribute "birthDay"', $documents['body']['message']); + + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::greaterThan('birthDay', '1960-01-01 10:10:10+02:30')->toString(), + ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1739,6 +2143,18 @@ trait DatabasesBase $this->assertEquals('1975-06-12T18:12:55.000+00:00', $documents['body']['documents'][1]['birthDay']); $this->assertCount(2, $documents['body']['documents']); + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::isNull('integers')->toString(), + ], + ]); + + $this->assertEquals(200, $documents['headers']['status-code']); + $this->assertEquals(1, $documents['body']['total']); + /** * Test for Failure */ @@ -1752,34 +2168,41 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("releaseYear", [' . implode(',', $conditions) . '])'], + 'queries' => [ + Query::equal('releaseYear', $conditions)->toString(), + ], ]); - $this->assertEquals(400, $documents['headers']['status-code']); + $this->assertEquals('Invalid query: Query on attribute has greater than 100 values: releaseYear', $documents['body']['message']); - $conditions = []; + $value = ''; for ($i = 0; $i < 101; $i++) { - $conditions[] = "[" . $i . "] Too long title to cross 2k chars query limit"; + $value .= "[" . $i . "] Too long title to cross 2k chars query limit "; } $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['search("title", ' . implode(',', $conditions) . ')'], + 'queries' => [ + Query::search('title', $value)->toString(), + ], ]); - $this->assertEquals(400, $documents['headers']['status-code']); + // Todo: Not sure what to do we with Query length Test VS old? JSON validator will fails if query string will be truncated? + //$this->assertEquals(400, $documents['headers']['status-code']); $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['search("actors", "Tom")'], + 'queries' => [ + Query::search('actors', 'Tom')->toString(), + ], ]); $this->assertEquals(400, $documents['headers']['status-code']); - $this->assertEquals('Searching by attribute "actors" requires a fulltext index.', $documents['body']['message']); + $this->assertEquals('Invalid query: Cannot query search on attribute "actors" because it is an array.', $documents['body']['message']); return []; } @@ -3520,9 +3943,9 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'equal("library", "library1")', - 'select(["fullName","library.*"])' - ] + Query::select(['fullName', 'library.*'])->toString(), + Query::equal('library', ['library1'])->toString(), + ], ]); $this->assertEquals(1, $documents['body']['total']); @@ -3534,7 +3957,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'equal("library.libraryName", ["Library 1"])', + Query::equal('library.libraryName', ['Library 1'])->toString(), ], ]); @@ -4040,10 +4463,10 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'isNotNull("$id")', - 'startsWith("fullName", "Stevie")', - 'endsWith("fullName", "Wonder")', - 'between("$createdAt", "1975-12-06", "2050-12-06")', + Query::isNotNull('$id')->toString(), + Query::startsWith('fullName', 'Stevie')->toString(), + Query::endsWith('fullName', 'Wonder')->toString(), + Query::between('$createdAt', '1975-12-06', '2050-12-0')->toString(), ], ]); @@ -4058,9 +4481,9 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'isNotNull("$id")', - 'isNull("fullName")', - 'select(["fullName"])' + Query::isNotNull('$id')->toString(), + Query::isNull('fullName')->toString(), + Query::select(['fullName'])->toString(), ], ]); @@ -4080,8 +4503,8 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'equal("fullName", "Stevie Wonder")', - 'select(["fullName"])' + Query::equal('fullName', ['Stevie Wonder'])->toString(), + Query::select(['fullName'])->toString(), ], ]); @@ -4093,7 +4516,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'select(["libraries.*", "$id"])', + Query::select(['libraries.*', '$id'])->toString(), ], ]); $document = $response['body']['documents'][0]; @@ -4105,7 +4528,7 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - 'select(["fullName", "$id"])' + Query::select(['fullName', '$id'])->toString(), ], ]); @@ -4275,7 +4698,9 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-timeout' => 1, ], $this->getHeaders()), [ - 'queries' => ['notEqual("longtext", "appwrite")'], + 'queries' => [ + Query::notEqual('longtext', 'appwrite')->toString(), + ], ]); $this->assertEquals(408, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php index c0369b6c9e..83fdc7f037 100644 --- a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php +++ b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php @@ -9,6 +9,7 @@ use Tests\E2E\Scopes\SideConsole; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; class DatabasesConsoleClientTest extends Scope { @@ -279,6 +280,7 @@ class DatabasesConsoleClientTest extends Scope /** * @depends testCreateCollection + * @throws \Utopia\Database\Exception\Query */ public function testGetCollectionLogs(array $data) { @@ -299,7 +301,7 @@ class DatabasesConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'] + 'queries' => [Query::limit(1)->toString()] ]); $this->assertEquals(200, $logs['headers']['status-code']); @@ -311,7 +313,7 @@ class DatabasesConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'] + 'queries' => [Query::offset(1)->toString()] ]); $this->assertEquals(200, $logs['headers']['status-code']); @@ -322,7 +324,7 @@ class DatabasesConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)', 'limit(1)'] + 'queries' => [Query::offset(1)->toString(), Query::limit(1)->toString()] ]); $this->assertEquals(200, $logs['headers']['status-code']); diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 648a4de800..1c5029d842 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -7,9 +7,11 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; use Tests\E2E\Client; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; class DatabasesCustomServerTest extends Scope { @@ -57,7 +59,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -66,7 +70,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -75,7 +81,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("name", ["Test 1", "Test 2"])'], + 'queries' => [ + Query::equal('name', ['Test 1', 'Test 2'])->toString(), + ], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(2, $databases['body']['databases']); @@ -84,7 +92,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("name", "Test 2")'], + 'queries' => [ + Query::equal('name', ['Test 2'])->toString(), + ], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -93,7 +103,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("$id", "first")'], + 'queries' => [ + Query::equal('$id', ['first'])->toString(), + ], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -105,7 +117,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderDesc("")'], + 'queries' => [ + Query::orderDesc()->toString(), + ], ]); $this->assertEquals(2, $databases['body']['total']); @@ -124,7 +138,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['databases'][0]['$id'] . '")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['databases'][0]['$id']]))->toString(), + ], ]); $this->assertCount(1, $databases['body']['databases']); @@ -134,7 +150,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['databases'][1]['$id'] . '")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['databases'][1]['$id']]))->toString(), + ], ]); $this->assertCount(0, $databases['body']['databases']); @@ -152,7 +170,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['databases'][1]['$id'] . '")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['databases'][1]['$id']]))->toString(), + ], ]); $this->assertCount(1, $databases['body']['databases']); @@ -162,7 +182,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['databases'][0]['$id'] . '")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['databases'][0]['$id']]))->toString(), + ], ]); $this->assertCount(0, $databases['body']['databases']); @@ -208,7 +230,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("unknown")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -375,7 +399,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -385,7 +411,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -395,7 +423,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("enabled", true)'] + 'queries' => [ + Query::equal('enabled', [true])->toString(), + ], ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -405,7 +435,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("enabled", false)'] + 'queries' => [ + Query::equal('enabled', [false])->toString(), + ], ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -418,7 +450,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderDesc("")'], + 'queries' => [ + Query::orderDesc()->toString(), + ], ]); $this->assertEquals(2, $collections['body']['total']); @@ -437,7 +471,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['collections'][0]['$id'] . '")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['collections'][0]['$id']]))->toString(), + ], ]); $this->assertCount(1, $collections['body']['collections']); @@ -447,7 +483,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $base['body']['collections'][1]['$id'] . '")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['collections'][1]['$id']]))->toString(), + ], ]); $this->assertCount(0, $collections['body']['collections']); @@ -465,7 +503,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['collections'][1]['$id'] . '")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['collections'][1]['$id']]))->toString(), + ], ]); $this->assertCount(1, $collections['body']['collections']); @@ -475,7 +515,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("' . $base['body']['collections'][0]['$id'] . '")'], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['collections'][0]['$id']]))->toString(), + ], ]); $this->assertCount(0, $collections['body']['collections']); @@ -521,7 +563,9 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("unknown")'], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -1376,7 +1420,7 @@ class DatabasesCustomServerTest extends Scope // Test indexLimit = 64 // MariaDB, MySQL, and MongoDB create 5 indexes per new collection // Add up to the limit, then check if the next index throws IndexLimitException - for ($i = 0; $i < 59; $i++) { + for ($i = 0; $i < 58; $i++) { // $this->assertEquals(true, static::getDatabase()->createIndex('indexLimit', "index{$i}", Database::INDEX_KEY, ["test{$i}"], [16])); $index = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1405,7 +1449,7 @@ class DatabasesCustomServerTest extends Scope $this->assertIsArray($collection['body']['attributes']); $this->assertIsArray($collection['body']['indexes']); $this->assertCount(64, $collection['body']['attributes']); - $this->assertCount(59, $collection['body']['indexes']); + $this->assertCount(58, $collection['body']['indexes']); $tooMany = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/indexes', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index c49608222d..8a5031f6ae 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -7,8 +7,10 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; class FunctionsCustomClientTest extends Scope { @@ -474,7 +476,9 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'queries' => [ 'limit(1)' ] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $executions['headers']['status-code']); @@ -485,7 +489,9 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'queries' => [ 'offset(1)' ] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $executions['headers']['status-code']); @@ -496,7 +502,9 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'queries' => [ 'equal("status", ["completed"])' ] + 'queries' => [ + Query::equal('status', ['completed'])->toString(), + ], ]); $this->assertEquals(200, $executions['headers']['status-code']); @@ -507,7 +515,9 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'queries' => [ 'equal("status", ["failed"])' ] + 'queries' => [ + Query::equal('status', ['failed'])->toString(), + ], ]); $this->assertEquals(200, $executions['headers']['status-code']); @@ -518,7 +528,9 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'queries' => [ 'cursorAfter("' . $base['body']['executions'][0]['$id'] . '")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $base['body']['executions'][0]['$id']]))->toString(), + ], ]); $this->assertCount(2, $executions['body']['executions']); @@ -529,7 +541,9 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'queries' => [ 'cursorBefore("' . $base['body']['executions'][1]['$id'] . '")' ], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $base['body']['executions'][1]['$id']]))->toString(), + ], ]); // Cleanup : Delete function diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index e39c6573e4..71d139a813 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -9,7 +9,9 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; class FunctionsCustomServerTest extends Scope @@ -121,7 +123,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -131,7 +135,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -141,7 +147,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("enabled", true)' ] + 'queries' => [ + Query::equal('enabled', [true])->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -151,7 +159,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("enabled", false)' ] + 'queries' => [ + Query::equal('enabled', [false])->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -244,7 +254,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $functions['body']['functions'][0]['$id'] . '")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $functions['body']['functions'][0]['$id']]))->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -255,7 +267,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorBefore("' . $functions['body']['functions'][1]['$id'] . '")' ], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $functions['body']['functions'][1]['$id']]))->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -269,7 +283,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("unknown")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -532,7 +548,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals($function['headers']['status-code'], 200); @@ -542,7 +560,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals($function['headers']['status-code'], 200); @@ -552,7 +572,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("entrypoint", "index.php")' ] + 'queries' => [ + Query::equal('entrypoint', ['index.php'])->toString(), + ], ]); $this->assertEquals($function['headers']['status-code'], 200); @@ -562,7 +584,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("entrypoint", "index.js")' ] + 'queries' => [ + Query::equal('entrypoint', ['index.js'])->toString(), + ], ]); $this->assertEquals($function['headers']['status-code'], 200); @@ -690,7 +714,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -700,7 +726,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -710,7 +738,9 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("trigger", "http")' ] + 'queries' => [ + Query::equal('trigger', ['http'])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -1459,7 +1489,10 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("resourceId", "' . $functionId . '")', 'equal("resourceType", "function")' ] + 'queries' => [ + Query::equal('resourceId', [$functionId])->toString(), + Query::equal('resourceType', ['function'])->toString(), + ], ]); $this->assertEquals(200, $rules['headers']['status-code']); diff --git a/tests/e2e/Services/GraphQL/AvatarsTest.php b/tests/e2e/Services/GraphQL/AvatarsTest.php index b95e3b251f..9f3fa7a3bb 100644 --- a/tests/e2e/Services/GraphQL/AvatarsTest.php +++ b/tests/e2e/Services/GraphQL/AvatarsTest.php @@ -29,7 +29,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(18767, \strlen($creditCardIcon['body'])); + $this->assertEquals(18546, \strlen($creditCardIcon['body'])); return $creditCardIcon['body']; } @@ -50,7 +50,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(11100, \strlen($browserIcon['body'])); + $this->assertEquals(13312, \strlen($browserIcon['body'])); return $browserIcon['body']; } @@ -71,7 +71,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(7460, \strlen($countryFlag['body'])); + $this->assertEquals(8814, \strlen($countryFlag['body'])); return $countryFlag['body']; } @@ -92,7 +92,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(36036, \strlen($image['body'])); + $this->assertEquals(52585, \strlen($image['body'])); return $image['body']; } @@ -134,7 +134,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(14771, \strlen($qrCode['body'])); + $this->assertEquals(29428, \strlen($qrCode['body'])); return $qrCode['body']; } @@ -155,7 +155,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(5041, \strlen($initials['body'])); + $this->assertEquals(5025, \strlen($initials['body'])); return $initials['body']; } diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 4683c3d8bf..0396895749 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -207,6 +207,7 @@ trait Base // Providers public static string $CREATE_MAILGUN_PROVIDER = 'create_mailgun_provider'; public static string $CREATE_SENDGRID_PROVIDER = 'create_sendgrid_provider'; + public static string $CREATE_SMTP_PROVIDER = 'create_smtp_provider'; public static string $CREATE_TWILIO_PROVIDER = 'create_twilio_provider'; public static string $CREATE_TELESIGN_PROVIDER = 'create_telesign_provider'; public static string $CREATE_TEXTMAGIC_PROVIDER = 'create_textmagic_provider'; @@ -218,6 +219,7 @@ trait Base public static string $GET_PROVIDER = 'get_provider'; public static string $UPDATE_MAILGUN_PROVIDER = 'update_mailgun_provider'; public static string $UPDATE_SENDGRID_PROVIDER = 'update_sendgrid_provider'; + public static string $UPDATE_SMTP_PROVIDER = 'update_smtp_provider'; public static string $UPDATE_TWILIO_PROVIDER = 'update_twilio_provider'; public static string $UPDATE_TELESIGN_PROVIDER = 'update_telesign_provider'; public static string $UPDATE_TEXTMAGIC_PROVIDER = 'update_textmagic_provider'; @@ -1809,6 +1811,16 @@ trait Base enabled } }'; + case self::$CREATE_SMTP_PROVIDER: + return 'mutation createSMTPProvider($providerId: String!, $name: String!, $host: String!, $port: Int!, $username: String!, $password: String!, $encryption: String!, $autoTLS: Boolean! $fromName: String!, $fromEmail: String!, $replyToName: String, $replyToEmail: String) { + messagingCreateSMTPProvider(providerId: $providerId, name: $name, host: $host, port: $port, username: $username, password: $password, encryption: $encryption, autoTLS: $autoTLS, fromName: $fromName, fromEmail: $fromEmail, replyToName: $replyToName, replyToEmail: $replyToEmail) { + _id + name + provider + type + enabled + } + }'; case self::$CREATE_TWILIO_PROVIDER: return 'mutation createTwilioProvider($providerId: String!, $name: String!, $from: String!, $accountSid: String!, $authToken: String!) { messagingCreateTwilioProvider(providerId: $providerId, name: $name, from: $from, accountSid: $accountSid, authToken: $authToken) { @@ -1923,6 +1935,16 @@ trait Base enabled } }'; + case self::$UPDATE_SMTP_PROVIDER: + return 'mutation updateSMTPProvider($providerId: String!, $name: String!, $host: String!, $port: Int!, $username: String!, $password: String!, $encryption: String!, $autoTLS: Boolean!, $fromName: String, $fromEmail: String, $enabled: Boolean) { + messagingUpdateSMTPProvider(providerId: $providerId, name: $name, host: $host, port: $port, username: $username, password: $password, encryption: $encryption, autoTLS: $autoTLS, fromName: $fromName, fromEmail: $fromEmail, enabled: $enabled) { + _id + name + provider + type + enabled + } + }'; case self::$UPDATE_TWILIO_PROVIDER: return 'mutation updateTwilioProvider($providerId: String!, $name: String!, $accountSid: String!, $authToken: String!) { messagingUpdateTwilioProvider(providerId: $providerId, name: $name, accountSid: $accountSid, authToken: $authToken) { @@ -2000,11 +2022,10 @@ trait Base } }'; case self::$CREATE_TOPIC: - return 'mutation createTopic($topicId: String!, $name: String!, $description: String!) { - messagingCreateTopic(topicId: $topicId, name: $name, description: $description) { + return 'mutation createTopic($topicId: String!, $name: String!) { + messagingCreateTopic(topicId: $topicId, name: $name) { _id name - description } }'; case self::$LIST_TOPICS: @@ -2014,7 +2035,6 @@ trait Base topics { _id name - description } } }'; @@ -2023,15 +2043,13 @@ trait Base messagingGetTopic(topicId: $topicId) { _id name - description } }'; case self::$UPDATE_TOPIC: - return 'mutation updateTopic($topicId: String!, $name: String!, $description: String!) { - messagingUpdateTopic(topicId: $topicId, name: $name, description: $description) { + return 'mutation updateTopic($topicId: String!, $name: String!) { + messagingUpdateTopic(topicId: $topicId, name: $name) { _id name - description } }'; case self::$DELETE_TOPIC: @@ -2098,8 +2116,8 @@ trait Base } }'; case self::$CREATE_EMAIL: - return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $cc: [String], $bcc: [String], $scheduledAt: String) { - messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, cc: $cc, bcc: $bcc, scheduledAt: $scheduledAt) { + return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $html: Boolean, $cc: [String], $bcc: [String], $scheduledAt: String) { + messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, html: $html, cc: $cc, bcc: $bcc, scheduledAt: $scheduledAt) { _id topics users @@ -2109,12 +2127,11 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$CREATE_SMS: - return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $scheduledAt: String) { - messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) { + return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $scheduledAt: String) { + messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, scheduledAt: $scheduledAt) { _id topics users @@ -2124,12 +2141,11 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$CREATE_PUSH_NOTIFICATION: - return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) { - messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) { + return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $scheduledAt: String) { + messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, scheduledAt: $scheduledAt) { _id topics users @@ -2139,7 +2155,6 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$LIST_MESSAGES: @@ -2157,7 +2172,6 @@ trait Base deliveryErrors deliveredTotal status - description } } }'; @@ -2174,12 +2188,11 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$UPDATE_EMAIL: - return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $cc: [String], $bcc: [String], $scheduledAt: String) { - messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, cc: $cc, bcc: $bcc, scheduledAt: $scheduledAt) { + return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, , $html: Boolean, $cc: [String], $bcc: [String], $scheduledAt: String) { + messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, html: $html, cc: $cc, bcc: $bcc, scheduledAt: $scheduledAt) { _id topics users @@ -2189,12 +2202,11 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$UPDATE_SMS: - return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $scheduledAt: String) { - messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) { + return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $scheduledAt: String) { + messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, scheduledAt: $scheduledAt) { _id topics users @@ -2204,12 +2216,11 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$UPDATE_PUSH_NOTIFICATION: - return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) { - messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) { + return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $scheduledAt: String) { + messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, scheduledAt: $scheduledAt) { _id topics users @@ -2219,7 +2230,6 @@ trait Base deliveryErrors deliveredTotal status - description } }'; case self::$COMPLEX_QUERY: diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 40134c2217..dba655b558 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -285,7 +285,6 @@ class MessagingTest extends Scope 'variables' => [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Active users', ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -296,7 +295,6 @@ class MessagingTest extends Scope $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']; } @@ -313,7 +311,6 @@ class MessagingTest extends Scope 'variables' => [ 'topicId' => $topicId, 'name' => 'topic2', - 'description' => 'Inactive users', ], ]; $response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -324,7 +321,6 @@ class MessagingTest extends Scope $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; } @@ -368,7 +364,6 @@ class MessagingTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('topic2', $response['body']['data']['messagingGetTopic']['name']); - $this->assertEquals('Inactive users', $response['body']['data']['messagingGetTopic']['description']); } /** @@ -594,7 +589,6 @@ class MessagingTest extends Scope 'variables' => [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Active users', ], ]; $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -801,7 +795,6 @@ class MessagingTest extends Scope 'variables' => [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Active users', ], ]; $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ @@ -1006,7 +999,6 @@ class MessagingTest extends Scope 'variables' => [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Active users', ], ]; $topic = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([ diff --git a/tests/e2e/Services/GraphQL/ScopeTest.php b/tests/e2e/Services/GraphQL/ScopeTest.php index a8b5b7cea4..ebf535268e 100644 --- a/tests/e2e/Services/GraphQL/ScopeTest.php +++ b/tests/e2e/Services/GraphQL/ScopeTest.php @@ -34,7 +34,7 @@ class ScopeTest extends Scope 'x-appwrite-key' => $apiKey, ], $gqlPayload); - $message = "app.${projectId}@service.localhost (role: applications) missing scope (databases.write)"; + $message = "app.{$projectId}@service.localhost (role: applications) missing scope (databases.write)"; $this->assertArrayHasKey('errors', $database['body']); $this->assertEquals($message, $database['body']['errors'][0]['message']); } diff --git a/tests/e2e/Services/GraphQL/UsersTest.php b/tests/e2e/Services/GraphQL/UsersTest.php index d243a45a4a..64d690a0fc 100644 --- a/tests/e2e/Services/GraphQL/UsersTest.php +++ b/tests/e2e/Services/GraphQL/UsersTest.php @@ -104,8 +104,8 @@ class UsersTest extends Scope 'query' => $query, 'variables' => [ 'queries' => [ - 'limit(100)', - 'offset(0)', + Query::limit(100)->toString(), + Query::offset(0)->toString(), ], ] ]; diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 1a16674658..d5ac040a75 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -6,6 +6,7 @@ use Appwrite\Enum\MessageStatus; use Tests\E2E\Client; use Utopia\App; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; use Utopia\DSN\DSN; trait MessagingBase @@ -28,6 +29,17 @@ trait MessagingBase 'fromEmail' => 'sender-email@my-domain.com', 'isEuRegion' => false, ], + 'smtp' => [ + 'providerId' => ID::unique(), + 'name' => 'SMTP1', + 'host' => 'smtp.appwrite.io', + 'port' => 587, + 'security' => 'tls', + 'username' => 'my-username', + 'password' => 'my-password', + 'fromName' => 'sender name', + 'fromEmail' => 'tester@appwrite.io', + ], 'twilio' => [ 'providerId' => ID::unique(), 'name' => 'Twilio1', @@ -114,6 +126,14 @@ trait MessagingBase 'apiKey' => 'my-apikey', 'domain' => 'my-domain', ], + 'smtp' => [ + 'name' => 'SMTP2', + 'host' => 'smtp.appwrite.io', + 'port' => 587, + 'security' => 'tls', + 'username' => 'my-username', + 'password' => 'my-password', + ], 'twilio' => [ 'name' => 'Twilio2', 'accountSid' => 'my-accountSid', @@ -185,6 +205,32 @@ trait MessagingBase return $providers; } + public function testUpdateProviderMissingCredentialsThrows(): void + { + // Create new FCM provider with no serviceAccountJSON + $response = $this->client->call(Client::METHOD_POST, '/messaging/providers/fcm', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'providerId' => ID::unique(), + 'name' => 'FCM3', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + // Enable provider with no serviceAccountJSON + $response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/fcm/' . $response['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'enabled' => true, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + } + /** * @depends testUpdateProviders */ @@ -197,7 +243,7 @@ trait MessagingBase ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(\count($providers), \count($response['body']['providers'])); + $this->assertEquals(11, \count($response['body']['providers'])); return $providers; } @@ -243,7 +289,6 @@ trait MessagingBase ]); $this->assertEquals(201, $response['headers']['status-code']); $this->assertEquals('my-app', $response['body']['name']); - $this->assertEquals('', $response['body']['description']); return $response['body']; } @@ -259,11 +304,9 @@ trait MessagingBase '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 $response['body']['$id']; } @@ -272,24 +315,13 @@ trait MessagingBase */ public function testListTopic(string $topicId) { - $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'], - ], [ - 'search' => 'updated-description', - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, \count($response['body']['topics'])); - $response = $this->client->call(Client::METHOD_GET, '/messaging/topics', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'queries' => [ - 'equal("total", [0])' + Query::equal('total', [0])->toString(), ], ]); @@ -302,7 +334,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'queries' => [ - 'greaterThan("total", 0)' + Query::greaterThan('total', 0)->toString(), ], ]); @@ -324,7 +356,6 @@ trait MessagingBase ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('android-app', $response['body']['name']); - $this->assertEquals('updated-description', $response['body']['description']); $this->assertEquals(0, $response['body']['total']); } @@ -381,7 +412,6 @@ trait MessagingBase $this->assertEquals(200, $topic['headers']['status-code']); $this->assertEquals('android-app', $topic['body']['name']); - $this->assertEquals('updated-description', $topic['body']['description']); $this->assertEquals(1, $topic['body']['total']); return [ @@ -493,7 +523,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['limit(1)'], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -506,7 +538,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['offset(1)'], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -518,7 +552,10 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['limit(1)', 'offset(1)'], + 'queries' => [ + Query::limit(1)->toString(), + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -534,7 +571,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['limit(-1)'] + 'queries' => [ + Query::limit(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -544,7 +583,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['offset(-1)'] + 'queries' => [ + Query::offset(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -554,7 +595,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['equal("$id", "asdf")'] + 'queries' => [ + Query::equal('$id', ['asdf'])->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -564,7 +607,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['orderAsc("$id")'] + 'queries' => [ + Query::orderAsc('$id')->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -574,7 +619,9 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'queries' => ['cursorAsc("$id")'] + 'queries' => [ + '{ "method": "cursorAsc", "attribute": "$id" }' + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -600,7 +647,6 @@ trait MessagingBase $this->assertEquals(200, $topic['headers']['status-code']); $this->assertEquals('android-app', $topic['body']['name']); - $this->assertEquals('updated-description', $topic['body']['description']); $this->assertEquals(0, $topic['body']['total']); } @@ -617,6 +663,60 @@ trait MessagingBase $this->assertEquals(204, $response['headers']['status-code']); } + /** + * @depends testCreateDraftEmail + */ + public function testListTargets(array $message) + { + $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/does_not_exist/targets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $message['$id'] . '/targets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $targetList = $response['body']; + $this->assertEquals(1, $targetList['total']); + $this->assertEquals(1, count($targetList['targets'])); + $this->assertEquals($message['targets'][0], $targetList['targets'][0]['$id']); + + // Test for empty targets + $response = $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' => ID::unique(), + 'subject' => 'New blog post', + 'content' => 'Check out the new blog post at http://localhost', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $message = $response['body']; + + $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $message['$id'] . '/targets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $targetList = $response['body']; + $this->assertEquals(0, $targetList['total']); + $this->assertEquals(0, count($targetList['targets'])); + } + public function testCreateDraftEmail() { // Create User @@ -634,7 +734,7 @@ trait MessagingBase $this->assertEquals(201, $response['headers']['status-code'], "Error creating user: " . var_export($response['body'], true)); $user = $response['body']; - var_dump($user); + $this->assertEquals(1, \count($user['targets'])); $targetId = $user['targets'][0]['$id']; @@ -698,7 +798,6 @@ trait MessagingBase ], [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Test Topic' ]); $this->assertEquals(201, $topic['headers']['status-code']); @@ -720,7 +819,6 @@ trait MessagingBase // Get target $target = $user['body']['targets'][0]; - // Create Subscriber $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ 'content-type' => 'application/json', @@ -759,14 +857,19 @@ trait MessagingBase $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); - return $message; + return [ + 'message' => $email['body'], + 'topic' => $topic['body'], + ]; } /** * @depends testSendEmail */ - public function testUpdateEmail(array $email): void + public function testUpdateEmail(array $params): void { + $email = $params['message']; + $message = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/email/' . $email['body']['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -855,7 +958,6 @@ trait MessagingBase ], [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Test Topic' ]); $this->assertEquals(201, $topic['headers']['status-code']); @@ -1016,7 +1118,6 @@ trait MessagingBase ], [ 'topicId' => ID::unique(), 'name' => 'topic1', - 'description' => 'Test Topic' ]); $this->assertEquals(201, $topic['headers']['status-code']); @@ -1144,56 +1245,50 @@ trait MessagingBase } /** - * @depends testCreateDraftEmail + * @depends testSendEmail + * @return void + * @throws \Exception */ - public function testListTargets(array $message) + public function testDeleteMessage(array $params): void { - $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/does_not_exist/targets', [ + $message = $params['message']; + $topic = $params['topic']; + + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $message['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ]); - $this->assertEquals(404, $response['headers']['status-code']); + $this->assertEquals(204, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $message['$id'] . '/targets', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - $targetList = $response['body']; - $this->assertEquals(1, $targetList['total']); - $this->assertEquals(1, count($targetList['targets'])); - $this->assertEquals($message['targets'][0], $targetList['targets'][0]['$id']); - - // Test for empty targets + // Test for FAILURE $response = $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' => ID::unique(), - 'subject' => 'New blog post', - 'content' => 'Check out the new blog post at http://localhost', + 'status' => 'processing', + 'topics' => [$topic['$id']], + 'subject' => 'Test subject', + 'content' => 'Test content', ]); - $this->assertEquals(201, $response['headers']['status-code']); - - $message = $response['body']; - - $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $message['$id'] . '/targets', [ + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $response['body']['$id'], [ '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(400, $response['headers']['status-code']); - $targetList = $response['body']; - $this->assertEquals(0, $targetList['total']); - $this->assertEquals(0, count($targetList['targets'])); + $response = $this->client->call(Client::METHOD_DELETE, '/messaging/messages/does_not_exist', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(404, $response['headers']['status-code']); } } diff --git a/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php b/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php index 0baa465b48..1b0d840f96 100644 --- a/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php +++ b/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php @@ -7,6 +7,7 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideConsole; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; class MessagingConsoleClientTest extends Scope { @@ -67,7 +68,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -79,7 +82,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -90,7 +95,10 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)', 'offset(1)'], + 'queries' => [ + Query::limit(1)->toString(), + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -105,7 +113,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(-1)'] + 'queries' => [ + Query::limit(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -114,7 +124,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(-1)'] + 'queries' => [ + Query::offset(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -123,7 +135,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("$id", "asdf")'] + 'queries' => [ + Query::equal('$id', ['asdf'])->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -132,7 +146,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("$id")'] + 'queries' => [ + Query::orderAsc('$id')->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -141,7 +157,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAsc("$id")'] + 'queries' => [ + '{ "method": "cursorAsc", "attribute":"$id" }' + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -170,7 +188,6 @@ class MessagingConsoleClientTest extends Scope ], $this->getHeaders()), [ 'topicId' => ID::unique(), 'name' => 'my-app', - 'description' => 'web app' ]); $this->assertEquals(201, $topic['headers']['status-code']); @@ -178,7 +195,7 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'description' => 'updated-description' + 'name' => 'android-app' ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -197,7 +214,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -209,7 +228,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -220,7 +241,10 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)', 'offset(1)'], + 'queries' => [ + Query::limit(1)->toString(), + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -235,7 +259,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(-1)'] + 'queries' => [ + Query::limit(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -244,7 +270,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(-1)'] + 'queries' => [ + Query::offset(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -253,7 +281,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("$id", "asdf")'] + 'queries' => [ + Query::equal('$id', ['asdf'])->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -262,7 +292,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("$id")'] + 'queries' => [ + Query::orderAsc('$id')->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -271,7 +303,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAsc("$id")'] + 'queries' => [ + '{"method":"cursorAsc","attribute":"$id","values":[]}' + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -331,7 +365,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -343,7 +379,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -354,7 +392,10 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)', 'offset(1)'], + 'queries' => [ + Query::limit(1)->toString(), + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -369,7 +410,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(-1)'] + 'queries' => [ + Query::limit(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -378,7 +421,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(-1)'] + 'queries' => [ + Query::offset(-1)->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -387,7 +432,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("$id", "asdf")'] + 'queries' => [ + Query::equal('$id', ['asdf'])->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -396,7 +443,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("$id")'] + 'queries' => [ + Query::orderAsc('$id')->toString(), + ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -405,7 +454,9 @@ class MessagingConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAsc("$id")'] + 'queries' => [ + '{"method":"cursorAsc","attribute":"$id","values":[]}' + ] ]); $this->assertEquals($response['headers']['status-code'], 400); diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index c89adad1d2..10b9f624af 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -10,7 +10,9 @@ use Tests\E2E\Scopes\SideClient; use Tests\E2E\Client; use Tests\E2E\General\UsageTest; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; class ProjectsConsoleClientTest extends Scope { @@ -288,7 +290,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("teamId", "' . $team['body']['$id'] . '")' ], + 'queries' => [ + Query::equal('teamId', [$team['body']['$id']])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -300,7 +304,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -312,7 +318,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(3)' ], + 'queries' => [ + Query::offset(3)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -324,7 +332,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("name", "Project Test 2")' ], + 'queries' => [ + Query::equal('name', ['Project Test 2'])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -336,7 +346,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'orderDesc("")' ], + 'queries' => [ + Query::orderDesc()->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -360,7 +372,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $response['body']['projects'][0]['$id'] . '")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $response['body']['projects'][0]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -372,7 +386,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorBefore("' . $response['body']['projects'][0]['$id'] . '")' ], + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $response['body']['projects'][0]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -387,7 +403,9 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("unknown")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], ]); $this->assertEquals(400, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index c4a15585eb..b5023a2afd 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -9,6 +9,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; trait StorageBase @@ -27,7 +28,7 @@ trait StorageBase 'name' => 'Test Bucket', 'fileSecurity' => true, 'maximumFileSize' => 2000000, //2MB - 'allowedFileExtensions' => ["jpg", "png", 'jfif'], + 'allowedFileExtensions' => ['jpg', 'png', 'jfif'], 'permissions' => [ Permission::read(Role::any()), Permission::create(Role::any()), @@ -380,7 +381,9 @@ trait StorageBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $files['headers']['status-code']); $this->assertEquals(1, count($files['body']['files'])); @@ -389,7 +392,9 @@ trait StorageBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $files['headers']['status-code']); $this->assertEquals(0, count($files['body']['files'])); @@ -398,7 +403,9 @@ trait StorageBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("mimeType", "image/png")' ] + 'queries' => [ + Query::equal('mimeType', ['image/png'])->toString(), + ], ]); $this->assertEquals(200, $files['headers']['status-code']); $this->assertEquals(1, count($files['body']['files'])); @@ -407,7 +414,9 @@ trait StorageBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("mimeType", "image/jpeg")' ] + 'queries' => [ + Query::equal('mimeType', ['image/jpeg'])->toString(), + ], ]); $this->assertEquals(200, $files['headers']['status-code']); $this->assertEquals(0, count($files['body']['files'])); diff --git a/tests/e2e/Services/Storage/StorageCustomServerTest.php b/tests/e2e/Services/Storage/StorageCustomServerTest.php index 38fb0e0b9d..585f91070c 100644 --- a/tests/e2e/Services/Storage/StorageCustomServerTest.php +++ b/tests/e2e/Services/Storage/StorageCustomServerTest.php @@ -6,7 +6,9 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; class StorageCustomServerTest extends Scope @@ -98,7 +100,9 @@ class StorageCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ], + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -108,7 +112,9 @@ class StorageCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -118,7 +124,9 @@ class StorageCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("$id", "bucket1")' ], + 'queries' => [ + Query::equal('$id', ['bucket1'])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -128,7 +136,9 @@ class StorageCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("fileSecurity", true)' ], + 'queries' => [ + Query::equal('fileSecurity', [true])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -138,7 +148,9 @@ class StorageCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $response['body']['buckets'][0]['$id'] . '")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $response['body']['buckets'][0]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 83b9042f13..6b93b01aad 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -4,7 +4,9 @@ namespace Tests\E2E\Services\Teams; use Tests\E2E\Client; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; trait TeamsBase @@ -147,7 +149,9 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(2)' ], + 'queries' => [ + Query::limit(2)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -157,7 +161,9 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ], + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -167,7 +173,9 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'greaterThanEqual("total", 0)' ], + 'queries' => [ + Query::greaterThanEqual('total', 0)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -177,7 +185,9 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("name", ["Arsenal", "Newcastle"])' ], + 'queries' => [ + Query::equal('name', ['Arsenal', 'Newcastle'])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -226,7 +236,9 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(2)' ], + 'queries' => [ + Query::limit(2)->toString(), + ], ]); $this->assertEquals(200, $teams['headers']['status-code']); @@ -238,7 +250,10 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)', 'cursorAfter("' . $teams['body']['teams'][0]['$id'] . '")' ], + 'queries' => [ + Query::limit(1)->toString(), + Query::cursorAfter(new Document(['$id' => $teams['body']['teams'][0]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -251,7 +266,10 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)', 'cursorBefore("' . $teams['body']['teams'][1]['$id'] . '")' ], + 'queries' => [ + Query::limit(1)->toString(), + Query::cursorBefore(new Document(['$id' => $teams['body']['teams'][1]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -267,7 +285,9 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("unknown")' ], + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], ]); $this->assertEquals(400, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index fa652fd30b..2213ae6d19 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -4,7 +4,9 @@ namespace Tests\E2E\Services\Teams; use Tests\E2E\Client; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; trait TeamsBaseClient @@ -40,7 +42,9 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ] + 'queries' => [ + Query::limit(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -50,7 +54,9 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] + 'queries' => [ + Query::offset(1)->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -60,7 +66,9 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("confirm", true)' ] + 'queries' => [ + Query::equal('confirm', [true])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -70,7 +78,9 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("confirm", false)' ] + 'queries' => [ + Query::equal('confirm', [false])->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -362,7 +372,9 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $memberships['body']['memberships'][0]['$id'] . '")' ] + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $memberships['body']['memberships'][0]['$id']]))->toString(), + ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -527,7 +539,7 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ]), [ 'secret' => $secret, - 'userId' => ID::custom(''), + 'userId' => ID::custom('asdf'), ]); $this->assertEquals(401, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index ddd6e22470..a1f19feb5f 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -5,7 +5,9 @@ namespace Tests\E2E\Services\Users; use Appwrite\Tests\Retry; use Appwrite\Utopia\Response; use Tests\E2E\Client; +use Utopia\Database\Document; use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; trait UsersBase { @@ -409,7 +411,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("name", "' . $user1['name'] . '")'] + 'queries' => [ + Query::equal('name', [$user1['name']])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -418,11 +422,14 @@ trait UsersBase $this->assertCount(1, $response['body']['users']); $this->assertEquals($response['body']['users'][0]['name'], $user1['name']); + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("email", "' . $user1['email'] . '")'] + 'queries' => [ + Query::equal('name', [$user1['name']])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -435,7 +442,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("status", true)'] + 'queries' => [ + Query::equal('status', [true])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -451,7 +460,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("status", false)'] + 'queries' => [ + Query::equal('status', [false])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -463,7 +474,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("passwordUpdate", "' . $user1['passwordUpdate'] . '")'] + 'queries' => [ + Query::equal('passwordUpdate', [$user1['passwordUpdate']])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -476,7 +489,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("registration", "' . $user1['registration'] . '")'] + 'queries' => [ + Query::equal('registration', [$user1['registration']])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -489,7 +504,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("emailVerification", false)'] + 'queries' => [ + Query::equal('emailVerification', [false])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -505,7 +522,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("emailVerification", true)'] + 'queries' => [ + Query::equal('emailVerification', [true])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -517,7 +536,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("phoneVerification", false)'] + 'queries' => [ + Query::equal('phoneVerification', [false])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -529,7 +550,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("phoneVerification", true)'] + 'queries' => [ + Query::equal('phoneVerification', [true])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -541,7 +564,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("' . $data['userId'] . '")'] + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $data['userId']]))->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -554,7 +579,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorBefore("user1")'] + 'queries' => [ + Query::cursorBefore(new Document(['$id' => 'user1']))->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -674,7 +701,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAfter("unknown")'] + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString() + ] ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -1203,7 +1232,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'], + 'queries' => [ + Query::limit(1)->toString() + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -1215,7 +1246,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'], + 'queries' => [ + Query::offset(1)->toString() + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -1226,7 +1259,10 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)', 'offset(1)'], + 'queries' => [ + Query::limit(1)->toString(), + Query::offset(1)->toString(), + ], ]); $this->assertEquals($logs['headers']['status-code'], 200); @@ -1241,7 +1277,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(-1)'] + 'queries' => [ + Query::limit(-1)->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -1250,7 +1288,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(-1)'] + 'queries' => [ + Query::offset(-1)->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -1259,7 +1299,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['equal("$id", "asdf")'] + 'queries' => [ + Query::equal('$id', ['asdf'])->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -1268,7 +1310,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['orderAsc("$id")'] + 'queries' => [ + Query::orderAsc('$id')->toString() + ] ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -1277,7 +1321,9 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['cursorAsc("$id")'] + 'queries' => [ + '{ "method": "cursorAsc", "attribute": "$id" }' + ] ]); $this->assertEquals($response['headers']['status-code'], 400); diff --git a/tests/resources/initials.png b/tests/resources/initials.png index ea6bf729b5..3e51f228cc 100644 Binary files a/tests/resources/initials.png and b/tests/resources/initials.png differ diff --git a/tests/resources/qr/qr-default.png b/tests/resources/qr/qr-default.png index a7da496d9f..58af53bb93 100644 Binary files a/tests/resources/qr/qr-default.png and b/tests/resources/qr/qr-default.png differ diff --git a/tests/resources/qr/qr-size-200-margin-10.png b/tests/resources/qr/qr-size-200-margin-10.png index 56c59408ef..b40529396e 100644 Binary files a/tests/resources/qr/qr-size-200-margin-10.png and b/tests/resources/qr/qr-size-200-margin-10.png differ diff --git a/tests/resources/qr/qr-size-200.png b/tests/resources/qr/qr-size-200.png index 202c0cc197..a44e4861e3 100644 Binary files a/tests/resources/qr/qr-size-200.png and b/tests/resources/qr/qr-size-200.png differ diff --git a/tests/unit/Utopia/Database/Validator/CustomIdTest.php b/tests/unit/Utopia/Database/Validator/CustomIdTest.php index 7989c4f555..e8c37a3f72 100644 --- a/tests/unit/Utopia/Database/Validator/CustomIdTest.php +++ b/tests/unit/Utopia/Database/Validator/CustomIdTest.php @@ -7,10 +7,7 @@ use PHPUnit\Framework\TestCase; class CustomIdTest extends TestCase { - /** - * @var Key - */ - protected $object = null; + protected ?CustomId $object = null; public function setUp(): void { diff --git a/tests/unit/Utopia/Database/Validator/ProjectIdTest.php b/tests/unit/Utopia/Database/Validator/ProjectIdTest.php index feff6da726..1b4b5384db 100644 --- a/tests/unit/Utopia/Database/Validator/ProjectIdTest.php +++ b/tests/unit/Utopia/Database/Validator/ProjectIdTest.php @@ -7,10 +7,7 @@ use PHPUnit\Framework\TestCase; class ProjectIdTest extends TestCase { - /** - * @var Key - */ - protected $object = null; + protected ?ProjectId $object = null; public function setUp(): void { diff --git a/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php b/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php index 3899ce0d80..cdeccef1bd 100644 --- a/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php +++ b/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php @@ -4,6 +4,8 @@ namespace Tests\Unit\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\Base; use PHPUnit\Framework\TestCase; +use Utopia\Database\Document; +use Utopia\Database\Query; class CollectionTest extends TestCase { @@ -25,17 +27,17 @@ class CollectionTest extends TestCase public function testValid(): void { $validator = new Base('users', ['name', 'search']); - $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::cursorAfter(new Document(['$id' => 'asdf']))]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('name', ['value'])]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::limit(10)]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::offset(10)]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::orderAsc('name')]), $validator->getDescription()); } public function testMissingIndex(): void { $validator = new Base('users', ['name']); - $this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription()); - $this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid([Query::equal('dne', ['value'])]), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid([Query::orderAsc('dne')]), $validator->getDescription()); } } diff --git a/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php b/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php index 122d83d885..327ae185cb 100644 --- a/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php +++ b/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\Users; use PHPUnit\Framework\TestCase; +use Utopia\Database\Query; class UsersTest extends TestCase { @@ -23,17 +24,17 @@ class UsersTest extends TestCase * Test for Success */ $this->assertEquals(true, $validator->isValid([]), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("email", "value")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("phone", "value")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['greaterThan("passwordUpdate", "2020-10-15 06:38")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['greaterThan("registration", "2020-10-15 06:38")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("emailVerification", true)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['equal("phoneVerification", true)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('name', ['value'])]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('email', ['value'])]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('phone', ['value'])]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::greaterThan('passwordUpdate', '2020-10-15 06:38')]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::greaterThan('registration', '2020-10-15 06:38')]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('emailVerification', [true])]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('phoneVerification', [true])]), $validator->getDescription()); /** * Test for Failure */ - $this->assertEquals(false, $validator->isValid(['equal("password", "value")']), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid([Query::equal('password', ['value'])]), $validator->getDescription()); } }