1
0
Fork 0
mirror of synced 2024-06-29 11:40:45 +12:00

Merge branch '0.16.x' of https://github.com/appwrite/appwrite into feat-improve-keys

This commit is contained in:
Christy Jacob 2022-08-30 17:35:46 +00:00
commit fe8295c2ec
75 changed files with 3464 additions and 505 deletions

View file

@ -52,6 +52,13 @@ $collections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
],
],
'collections' => [
@ -150,6 +157,27 @@ $collections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_documentSecurity'),
'type' => Database::INDEX_KEY,
'attributes' => ['documentSecurity'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -1326,6 +1354,13 @@ $collections = [
]
],
'indexes' => [
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_email'),
'type' => Database::INDEX_UNIQUE,
@ -1340,6 +1375,41 @@ $collections = [
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['status'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_passwordUpdate'),
'type' => Database::INDEX_KEY,
'attributes' => ['passwordUpdate'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_registration'),
'type' => Database::INDEX_KEY,
'attributes' => ['registration'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_emailVerification'),
'type' => Database::INDEX_KEY,
'attributes' => ['emailVerification'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_phoneVerification'),
'type' => Database::INDEX_KEY,
'attributes' => ['phoneVerification'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
@ -1779,6 +1849,20 @@ $collections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_total'),
'type' => Database::INDEX_KEY,
'attributes' => ['total'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -1927,6 +2011,41 @@ $collections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_userId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_teamId'),
'type' => Database::INDEX_KEY,
'attributes' => ['teamId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_invited'),
'type' => Database::INDEX_KEY,
'attributes' => ['invited'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_joined'),
'type' => Database::INDEX_KEY,
'attributes' => ['joined'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_confirm'),
'type' => Database::INDEX_KEY,
'attributes' => ['confirm'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -2075,7 +2194,63 @@ $collections = [
'attributes' => ['search'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
]
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['status'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_runtime'),
'type' => Database::INDEX_KEY,
'attributes' => ['runtime'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_deployment'),
'type' => Database::INDEX_KEY,
'attributes' => ['deployment'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_schedule'),
'type' => Database::INDEX_KEY,
'attributes' => ['schedule'],
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_scheduleNext'),
'type' => Database::INDEX_KEY,
'attributes' => ['scheduleNext'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_schedulePrevious'),
'type' => Database::INDEX_KEY,
'attributes' => ['schedulePrevious'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_timeout'),
'type' => Database::INDEX_KEY,
'attributes' => ['timeout'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -2228,6 +2403,34 @@ $collections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_entrypoint'),
'type' => Database::INDEX_KEY,
'attributes' => ['entrypoint'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_size'),
'type' => Database::INDEX_KEY,
'attributes' => ['size'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_buildId'),
'type' => Database::INDEX_KEY,
'attributes' => ['buildId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_activate'),
'type' => Database::INDEX_KEY,
'attributes' => ['activate'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -2500,6 +2703,34 @@ $collections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_trigger'),
'type' => Database::INDEX_KEY,
'attributes' => ['trigger'],
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_status'),
'type' => Database::INDEX_KEY,
'attributes' => ['status'],
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_statusCode'),
'type' => Database::INDEX_KEY,
'attributes' => ['statusCode'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_time'),
'type' => Database::INDEX_KEY,
'attributes' => ['time'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@ -2691,6 +2922,48 @@ $collections = [
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [128],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_fileSecurity'),
'type' => Database::INDEX_KEY,
'attributes' => ['fileSecurity'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_maximumFileSize'),
'type' => Database::INDEX_KEY,
'attributes' => ['maximumFileSize'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_encryption'),
'type' => Database::INDEX_KEY,
'attributes' => ['encryption'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_antivirus'),
'type' => Database::INDEX_KEY,
'attributes' => ['antivirus'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
]
],
@ -3093,6 +3366,48 @@ $collections = [
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_signature'),
'type' => Database::INDEX_KEY,
'attributes' => ['signature'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_mimeType'),
'type' => Database::INDEX_KEY,
'attributes' => ['mimeType'],
'lengths' => [127],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_sizeOriginal'),
'type' => Database::INDEX_KEY,
'attributes' => ['sizeOriginal'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_chunksTotal'),
'type' => Database::INDEX_KEY,
'attributes' => ['chunksTotal'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_chunksUploaded'),
'type' => Database::INDEX_KEY,
'attributes' => ['chunksUploaded'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
]
],
];

View file

@ -121,6 +121,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'etsy' => [
'name' => 'Etsy',
'developers' => 'https://developers.etsy.com/',
'icon' => 'icon-etsy',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'facebook' => [
'name' => 'Facebook',
'developers' => 'https://developers.facebook.com/',

View file

@ -59,9 +59,12 @@ return [
'home',
'console',
'documents.read',
'documents.write',
'files.read',
'files.write',
'locale.read',
'avatars.read',
'execution.write',
],
],
Auth::USER_ROLE_USERS => [

View file

@ -18,6 +18,9 @@ use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Template\Template;
use Appwrite\URL\URL as URLParser;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
@ -1345,14 +1348,18 @@ App::get('/v1/account/logs')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Queries(new Limit(), new Offset()), '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 supported methods are limit and offset', true)
->inject('response')
->inject('user')
->inject('locale')
->inject('geodb')
->inject('dbForProject')
->action(function (int $limit, int $offset, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
$audit = new EventAudit($dbForProject);

View file

@ -24,7 +24,6 @@ use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\Query as QueryValidator;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\UID;
use Utopia\Database\Exception\Authorization as AuthorizationException;
@ -37,11 +36,16 @@ use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\IP;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries as QueriesValidator;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Response;
use Appwrite\Detector\Detector;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Collections;
use Appwrite\Utopia\Database\Validator\Queries\Databases;
use Appwrite\Utopia\Database\Validator\Queries\Documents;
use Utopia\Config\Config;
use MaxMind\Db\Reader;
@ -229,38 +233,36 @@ App::get('/v1/databases')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DATABASE_LIST)
->param('queries', [], new Databases(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Databases::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of collection to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the collection used as the starting point for the query, excluding the collection itself. Should be used for efficient pagination when working with large sets of data.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === 'ASC' ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('databases', $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$databaseId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('databases', $databaseId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Database '{$databaseId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'databases' => $dbForProject->find('databases', \array_merge($filterQueries, $queries)),
'databases' => $dbForProject->find('databases', $queries),
'total' => $dbForProject->count('databases', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_DATABASE_LIST);
});
@ -292,24 +294,23 @@ App::get('/v1/databases/:databaseId')
});
App::get('/v1/databases/:databaseId/logs')
->desc('List Collection Logs')
->desc('List Database Logs')
->groups(['api', 'database'])
->label('scope', 'collections.read')
->label('scope', 'databases.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
->label('sdk.method', 'listLogs')
->label('sdk.description', '/docs/references/databases/get-collection-logs.md')
->label('sdk.description', '/docs/references/databases/get-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Queries(new Limit(), new Offset()), '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 supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $databaseId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$database = $dbForProject->getDocument('databases', $databaseId);
@ -317,9 +318,17 @@ App::get('/v1/databases/:databaseId/logs')
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
@ -538,15 +547,11 @@ App::get('/v1/databases/:databaseId/collections')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_COLLECTION_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('queries', [], new Collections(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Collections::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of collection to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the collection used as the starting point for the query, excluding the collection itself. Should be used for efficient pagination when working with large sets of data.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $databaseId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@ -554,30 +559,30 @@ App::get('/v1/databases/:databaseId/collections')
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === 'ASC' ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$collectionId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$collectionId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER
? Query::cursorAfter($cursorDocument)
: Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'collections' => $dbForProject->find('database_' . $database->getInternalId(), \array_merge($filterQueries, $queries)),
'collections' => $dbForProject->find('database_' . $database->getInternalId(), $queries),
'total' => $dbForProject->count('database_' . $database->getInternalId(), $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_COLLECTION_LIST);
});
@ -633,13 +638,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Queries(new Limit(), new Offset()), '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 supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $databaseId, string $collectionId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@ -653,6 +657,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/collection/' . $collectionId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
@ -1802,6 +1811,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('usage.metric', 'documents.{scope}.requests.create')
->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}'])
->label('abuse-limit', 120)
->label('abuse-time', 60)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'databases')
->label('sdk.method', 'createDocument')
@ -1931,17 +1942,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->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/database#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. By default will return maximum 25 results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderAttributes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes used to sort results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order attributes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('orderTypes', [], new ArrayList(new WhiteList([Database::ORDER_DESC, Database::ORDER_ASC], true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of order directions for sorting attributes. Possible values are DESC for descending order or ASC for ascending order. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order types are allowed.', true)
->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). 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, int $limit, int $offset, string $cursor, string $cursorDirection, array $orderAttributes, array $orderTypes, Response $response, Database $dbForProject, string $mode) {
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@ -1964,52 +1969,41 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$filterQueries = \array_map(function ($query) {
$query = Query::parse($query);
if (\count($query->getValues()) > 100) {
throw new Exception(Exception::GENERAL_QUERY_LIMIT_EXCEEDED, "You cannot use more than 100 query values on attribute '{$query->getAttribute()}'");
}
return $query;
}, $queries);
$otherQueries = [];
$otherQueries[] = Query::limit($limit);
$otherQueries[] = Query::offset($offset);
foreach ($orderTypes as $i => $orderType) {
$otherQueries[] = $orderType === Database::ORDER_DESC ? Query::orderDesc($orderAttributes[$i] ?? '') : Query::orderAsc($orderAttributes[$i] ?? '');
// Validate queries
$queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes'));
$validQueries = $queriesValidator->isValid($queries);
if (!$validQueries) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription());
}
if (!empty($cursor)) {
$queries = Query::parseQueries($queries);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$documentId = $cursor->getValue();
if ($documentSecurity && !$valid) {
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor);
$cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
} else {
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor));
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
}
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$documentId}' for the 'cursor' value not found.");
}
$otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$allQueries = \array_merge($filterQueries, $otherQueries);
if (!empty($allQueries)) {
$attributes = $collection->getAttribute('attributes', []);
$validator = new QueriesValidator(new QueryValidator($attributes), $attributes, $collection->getAttribute('indexes', []), true);
if (!$validator->isValid($allQueries)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
}
$filterQueries = Query::groupByType($queries)['filters'];
if ($documentSecurity && !$valid) {
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries);
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries));
$documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
@ -2102,13 +2096,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', null, new UID(), 'Document ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Queries(new Limit(), new Offset()), '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 supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $databaseId, string $collectionId, string $documentId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@ -2128,6 +2121,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/collection/' . $collectionId . '/document/' . $document->getId();
$logs = $audit->getLogsByResource($resource, $limit, $offset);
@ -2191,6 +2189,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}')
->label('usage.metric', 'documents.{scope}.requests.update')
->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}'])
->label('abuse-limit', 60)
->label('abuse-time', 60)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'databases')
->label('sdk.method', 'updateDocument')
@ -2320,6 +2320,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}')
->label('usage.metric', 'documents.{scope}.requests.delete')
->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}'])
->label('abuse-limit', 60)
->label('abuse-time', 60)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'databases')
->label('sdk.method', 'deleteDocument')

View file

@ -22,6 +22,9 @@ use Utopia\Storage\Validator\Upload;
use Appwrite\Utopia\Response;
use Utopia\Swoole\Request;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
use Appwrite\Utopia\Database\Validator\Queries\Executions;
use Appwrite\Utopia\Database\Validator\Queries\Functions;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
@ -102,38 +105,36 @@ App::get('/v1/functions')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION_LIST)
->param('queries', [], new Functions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Functions::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of functions to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('functions', $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$functionId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('functions', $functionId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$functionId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'functions' => $dbForProject->find('functions', \array_merge($filterQueries, $queries)),
'functions' => $dbForProject->find('functions', $queries),
'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_FUNCTION_LIST);
});
@ -768,15 +769,11 @@ App::get('/v1/functions/:functionId/deployments')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT_LIST)
->param('functionId', '', new UID(), 'Function ID.')
->param('queries', [], new Deployments(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Deployments::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of deployments to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the deployment used as the starting point for the query, excluding the deployment itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) {
$function = $dbForProject->getDocument('functions', $functionId);
@ -784,30 +781,33 @@ App::get('/v1/functions/:functionId/deployments')
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$filterQueries[] = Query::equal('resourceId', [$function->getId()]);
$filterQueries[] = Query::equal('resourceType', ['functions']);
// Set resource queries
$queries[] = Query::equal('resourceId', [$function->getId()]);
$queries[] = Query::equal('resourceType', ['functions']);
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('deployments', $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$deploymentId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$results = $dbForProject->find('deployments', \array_merge($filterQueries, $queries));
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('deployments', $queries);
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
foreach ($results as $result) {
@ -1123,14 +1123,11 @@ App::get('/v1/functions/:functionId/executions')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION_LIST)
->param('functionId', '', new UID(), 'Function ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of executions to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Executions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('cursor', '', new UID(), 'ID of the execution used as the starting point for the query, excluding the execution itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, int $limit, int $offset, string $search, string $cursor, string $cursorDirection, Response $response, Database $dbForProject) {
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) {
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
@ -1138,29 +1135,32 @@ App::get('/v1/functions/:functionId/executions')
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$filterQueries = [
Query::equal('functionId', [$function->getId()])
];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('executions', $cursor);
// Set internal queries
$queries[] = Query::equal('functionId', [$function->getId()]);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$executionId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('executions', $executionId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Execution '{$executionId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$results = $dbForProject->find('executions', \array_merge($filterQueries, $queries));
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('executions', $queries);
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
$roles = Authorization::getRoles();

View file

@ -25,6 +25,8 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\Queries\Buckets;
use Appwrite\Utopia\Database\Validator\Queries\Files;
use Utopia\Image\Image;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Device;
@ -150,38 +152,36 @@ App::get('/v1/storage/buckets')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET_LIST)
->param('queries', [], new Buckets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Buckets::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('cursor', '', new UID(), 'ID of the bucket used as the starting point for the query, excluding the bucket itself. Should be used for efficient pagination when working with large sets of data.', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('name', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('buckets', $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$bucketId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('buckets', $bucketId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Bucket '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Bucket '{$bucketId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'buckets' => $dbForProject->find('buckets', \array_merge($filterQueries, $queries)),
'buckets' => $dbForProject->find('buckets', $queries),
'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_BUCKET_LIST);
});
@ -326,6 +326,8 @@ App::post('/v1/storage/buckets/:bucketId/files')
->label('audits.resource', 'files/{response.$id}')
->label('usage.metric', 'files.{scope}.requests.create')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-limit', 60)
->label('abuse-time', 60)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createFile')
@ -649,16 +651,12 @@ App::get('/v1/storage/buckets/:bucketId/files')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE_LIST)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of files to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->inject('mode')
->action(function (string $bucketId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, string $mode) {
->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -673,35 +671,38 @@ App::get('/v1/storage/buckets/:bucketId/files')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('name', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$fileId = $cursor->getValue();
if ($fileSecurity && !$valid) {
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor);
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor));
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$fileId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries));
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries)));
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
@ -1204,6 +1205,8 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->label('audits.resource', 'files/{response.$id}')
->label('usage.metric', 'files.{scope}.requests.update')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-limit', 60)
->label('abuse-time', 60)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateFile')
@ -1303,6 +1306,8 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->label('audits.resource', 'file/{request.fileId}')
->label('usage.metric', 'files.{scope}.requests.delete')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-limit', 60)
->label('abuse-time', 60)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteFile')
@ -1359,9 +1364,12 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->setResource('file/' . $fileId)
;
// Don't need to check valid here because we already ensured validity
if ($fileSecurity) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($fileSecurity && !$valid) {
try {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
} else {
$deleted = Authorization::skip(fn() => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
}

View file

@ -10,6 +10,11 @@ use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Host;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
@ -74,6 +79,10 @@ App::post('/v1/teams')
])));
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
if (!\in_array('owner', $roles)) {
$roles[] = 'owner';
}
$membershipId = ID::unique();
$membership = new Document([
'$id' => $membershipId,
@ -122,37 +131,35 @@ App::get('/v1/teams')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of teams to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the team used as the starting point for the query, excluding the team itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('teams', $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$teamId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('teams', $teamId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$teamId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$results = $dbForProject->find('teams', \array_merge($filterQueries, $queries));
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('teams', $queries);
$total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
@ -460,15 +467,11 @@ App::get('/v1/teams/:teamId/memberships')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
->param('teamId', '', new UID(), 'Team ID.')
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of memberships to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the membership used as the starting point for the query, excluding the membership itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $teamId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (string $teamId, array $queries, string $search, Response $response, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -476,29 +479,34 @@ App::get('/v1/teams/:teamId/memberships')
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$filterQueries = [Query::equal('teamId', [$teamId])];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$otherQueries = [];
$otherQueries[] = Query::limit($limit);
$otherQueries[] = Query::offset($offset);
$otherQueries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('memberships', $cursor);
// Set internal queries
$queries[] = Query::equal('teamId', [$teamId]);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$membershipId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$membershipId}' for the 'cursor' value not found.");
}
$otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$memberships = $dbForProject->find(
collection: 'memberships',
queries: \array_merge($filterQueries, $otherQueries),
queries: $queries,
);
$total = $dbForProject->count(
@ -847,18 +855,12 @@ App::get('/v1/teams/:teamId/logs')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('teamId', null, new UID(), 'Team ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Queries(new Limit(), new Offset()), '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 supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function ($teamId, $limit, $offset, $response, $dbForProject, $locale, $geodb) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -866,6 +868,11 @@ App::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'team/' . $team->getId();
$logs = $audit->getLogsByResource($resource, $limit, $offset);

View file

@ -8,6 +8,10 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Audit\Audit;
@ -27,7 +31,6 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Assoc;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Text;
use Utopia\Validator\Range;
use Utopia\Validator\Boolean;
use MaxMind\Db\Reader;
use Utopia\Validator\Integer;
@ -340,38 +343,36 @@ App::get('/v1/users')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER_LIST)
->param('queries', [], new Users(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of users to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
$filterQueries = [];
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$filterQueries[] = Query::search('search', $search);
$queries[] = Query::search('search', $search);
}
$queries = [];
$queries[] = Query::limit($limit);
$queries[] = Query::offset($offset);
$queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc('');
if (!empty($cursor)) {
$cursorDocument = $dbForProject->getDocument('users', $cursor);
// Get cursor document if there was a cursor query
$cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE));
if ($cursor) {
/** @var Query $cursor */
$userId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('users', $userId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "User '{$cursor}' for the 'cursor' value not found.");
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "User '{$userId}' for the 'cursor' value not found.");
}
$queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument);
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'users' => $dbForProject->find('users', \array_merge($filterQueries, $queries)),
'users' => $dbForProject->find('users', $queries),
'total' => $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_USER_LIST);
});
@ -525,13 +526,12 @@ App::get('/v1/users/:userId/logs')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('userId', '', new UID(), 'User ID.')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('queries', [], new Queries(new Limit(), new Offset()), '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 supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $userId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$user = $dbForProject->getDocument('users', $userId);
@ -539,6 +539,11 @@ App::get('/v1/users/:userId/logs')
throw new Exception(Exception::USER_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getId(), $limit, $offset);
@ -621,8 +626,7 @@ App::patch('/v1/users/:userId/status')
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
$events
->setParam('userId', $user->getId())
;
->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
});
@ -657,8 +661,7 @@ App::patch('/v1/users/:userId/verification')
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
$events
->setParam('userId', $user->getId())
;
->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
});
@ -693,8 +696,7 @@ App::patch('/v1/users/:userId/verification/phone')
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('phoneVerification', $phoneVerification));
$events
->setParam('userId', $user->getId())
;
->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_USER);
});
@ -813,8 +815,7 @@ App::patch('/v1/users/:userId/email')
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]))
;
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]));
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@ -935,8 +936,7 @@ App::patch('/v1/users/:userId/prefs')
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$events
->setParam('userId', $user->getId())
;
->setParam('userId', $user->getId());
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
@ -978,8 +978,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
$events
->setParam('userId', $user->getId())
->setParam('sessionId', $sessionId)
;
->setParam('sessionId', $sessionId);
$response->noContent();
});
@ -1011,7 +1010,8 @@ App::delete('/v1/users/:userId/sessions')
$sessions = $user->getAttribute('sessions', []);
foreach ($sessions as $key => $session) { /** @var Document $session */
foreach ($sessions as $key => $session) {
/** @var Document $session */
$dbForProject->deleteDocument('sessions', $session->getId());
//TODO: fix this
}
@ -1020,8 +1020,7 @@ App::delete('/v1/users/:userId/sessions')
$events
->setParam('userId', $user->getId())
->setPayload($response->output($user, Response::MODEL_USER))
;
->setPayload($response->output($user, Response::MODEL_USER));
$response->noContent();
});
@ -1059,13 +1058,11 @@ App::delete('/v1/users/:userId')
$deletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($clone)
;
->setDocument($clone);
$events
->setParam('userId', $user->getId())
->setPayload($response->output($clone, Response::MODEL_USER))
;
->setPayload($response->output($clone, Response::MODEL_USER));
$response->noContent();
});
@ -1081,7 +1078,7 @@ App::get('/v1/users/usage')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->inject('response')
->inject('dbForProject')
->inject('register')

View file

@ -294,6 +294,7 @@ App::get('/console/databases/collection')
$permissions
->setParam('method', 'databases.getCollection')
->setParam('events', 'load,databases.updateCollection')
->setParam('form', 'collectionPermissions')
->setParam('data', 'project-collection')
->setParam('params', [
'collection-id' => '{{router.params.id}}',
@ -329,7 +330,6 @@ App::get('/console/databases/document')
->action(function (string $databaseId, string $collection, View $layout) {
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
$logs
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
->setParam('method', 'databases.listDocumentLogs')
@ -341,15 +341,16 @@ App::get('/console/databases/document')
;
$permissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$permissions
->setParam('method', 'databases.getDocument')
->setParam('events', 'load,databases.updateDocument')
->setParam('form', 'documentPermissions')
->setParam('data', 'project-document')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
))
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'collection-id' => '{{router.params.collection}}',
'database-id' => '{{router.params.databaseId}}',
@ -384,10 +385,12 @@ App::get('/console/databases/document/new')
$permissions
->setParam('data', 'project-document')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
))
->setParam('form', 'documentPermissions')
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'collection-id' => '{{router.params.collection}}',
'database-id' => '{{router.params.databaseId}}',
@ -451,20 +454,22 @@ App::get('/console/storage/bucket')
$fileCreatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$fileCreatePermissions
->setParam('form', 'fileCreatePermissions')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
));
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
]);
$fileUpdatePermissions = new View(__DIR__ . '/../../views/console/comps/permissions-matrix.phtml');
$fileUpdatePermissions
->setParam('method', 'storage.getFile')
->setParam('data', 'file')
->setParam('form', 'fileUpdatePermissions')
->setParam('permissions', \array_filter(
Database::PERMISSIONS,
fn ($perm) => $perm != Database::PERMISSION_CREATE
))
->setParam('permissions', [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
])
->setParam('params', [
'bucket-id' => '{{router.params.id}}',
]);

View file

@ -7,7 +7,7 @@ $params = $this->getParam('params', []);
$events = $this->getParam('events', '');
$permissions = $this->getParam('permissions', Database::PERMISSIONS);
$data = $this->getParam('data', '');
$form = $this->getParam('form', 'form');
$form = $this->getParam('form');
$escapedPermissions = \array_map(function ($perm) {
// Alpine won't bind to a parameter named delete
@ -34,7 +34,7 @@ $escapedPermissions = \array_map(function ($perm) {
<?php if (!empty($data)): ?>
data-name="<?php echo $data; ?>"
<?php endif; ?>
@reset.window="permissions = rawPermissions = []">
@reset.window="permissions.length = rawPermissions.length = 0">
<input
type="hidden"
@ -79,7 +79,7 @@ $escapedPermissions = \array_map(function ($perm) {
</tr>
</template>
<tr x-data="permissionsRow"
@addrow.window="addPermission('<?php echo $form; ?>',role,{<?php echo \implode(',', $escapedPermissions) ?>})">
@addrow<?php echo \strtolower($form); ?>.window="addPermission('<?php echo $form; ?>', role, { <?php echo \implode(',', $escapedPermissions) ?> })">
<td>
<datalist id="types">
<option value="user:">
@ -91,7 +91,7 @@ $escapedPermissions = \array_map(function ($perm) {
<input
required
id="<?php echo $form; ?>"
id="<?php echo $form; ?>Input"
name="<?php echo $form; ?>"
form="<?php echo $form ?>"
list="types"
@ -109,7 +109,7 @@ $escapedPermissions = \array_map(function ($perm) {
<tfoot>
<tr>
<td colspan="<?php \count($permissions) + 2 ?>">
<button type="button" class="btn btn-primary margin-top-small" @click="$dispatch('addrow')">Add</button>
<button type="button" class="margin-top-small reverse" @click="$dispatch('addrow<?php echo \strtolower($form); ?>')">Add</button>
</td>
</tr>
</tfoot>

View file

@ -514,7 +514,7 @@ $permissions = $this->getParam('permissions', null);
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
<form id="<?php echo $permissions->getParam('form') ?>"></form>
<form
data-analytics

View file

@ -53,7 +53,7 @@ $permissions = $this->getParam('permissions', null);
<div class="row responsive">
<div class="col span-8 margin-bottom">
<form id="<?php echo $permissions->getParam('form', 'permissions') ?>"></form>
<form id="<?php echo $permissions->getParam('form') ?>"></form>
<form
data-analytics

View file

@ -108,7 +108,7 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
<td class="col-name" data-title="Name: " data-ls-attrs="title={{file.name}}" >
<div class="trim-inner-text">
<span data-ls-ui-trigger="modal-file-update-{{file.$id}}" class="link text-one-liner" data-ls-bind="{{file.name}}"></span>
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal sticky-footer width-large close" >
<div data-ls-attrs="data-open-event=modal-file-update-{{file.$id}}" data-button-hide="1" data-ui-modal class="box modal width-xlarge sticky-footer close" >
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update File</h1>
@ -139,12 +139,11 @@ $fileUpdatePermissions = $this->getParam('fileUpdatePermissions', null);
</div>
<input type="hidden" data-ls-attrs="id=file-bucketId-{{file.$id}}" name="bucketId" data-ls-bind="{{file.bucketId}}">
<div class="toggle margin-bottom" data-ls-if="{{project-bucket.fileSecurity}}" data-ls-ui-open data-button-aria="Open Permissions">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<hr class="margin-start margin-end" />
<h3 class="margin-bottom-large">Permissions</h3>
<h3 class="margin-bottom">Permissions</h3>
<div class="margin-start margin-end margin-bottom">
<?php echo $fileUpdatePermissions->render(); ?>
</div>
</form>

10
composer.lock generated
View file

@ -2841,12 +2841,12 @@
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "532d4d15ec8f11539972f2848c2e230c49b24f65"
"reference": "6e630a62f522ac68a7056bebf81cd032c7a053ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/532d4d15ec8f11539972f2848c2e230c49b24f65",
"reference": "532d4d15ec8f11539972f2848c2e230c49b24f65",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6e630a62f522ac68a7056bebf81cd032c7a053ba",
"reference": "6e630a62f522ac68a7056bebf81cd032c7a053ba",
"shasum": ""
},
"require": {
@ -2883,7 +2883,7 @@
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/feat-new-headers"
},
"time": "2022-08-20T07:42:55+00:00"
"time": "2022-08-29T10:43:33+00:00"
},
{
"name": "doctrine/instantiator",
@ -5383,5 +5383,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.2.0"
}

View file

@ -0,0 +1 @@
Get the database activity logs list by its unique ID.

View file

@ -4054,11 +4054,13 @@ if(this.attribute){event+=`.${this.attribute}`;}
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
if(existing===undefined){this.permissions.push({role,[type]:true,});}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}

View file

@ -609,11 +609,13 @@ if(this.attribute){event+=`.${this.attribute}`;}
this.events.add(event);this.reset();},removeEvent(value){this.events.delete(value);}}));});})(window);(function(window){document.addEventListener('alpine:init',()=>{Alpine.data('permissionsMatrix',()=>({permissions:[],rawPermissions:[],load(permissions){if(permissions===undefined){return;}
this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePermission(p);type=this.parseInputPermission(type);let index=-1;let existing=this.permissions.find((p,idx)=>{if(p.role===role){index=idx;return true;}})
if(existing===undefined){this.permissions.push({role,[type]:true,});}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!document.getElementById(formId).reportValidity()){return;}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId,role,permissions){if(!this.validate(formId,role,permissions)){return;}
Object.entries(permissions).forEach(entry=>{let[type,enabled]=entry;type=this.parseOutputPermission(type);if(enabled){this.rawPermissions.push(this.buildPermission(type,role));}});this.permissions.push({role,...permissions,});this.reset();},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
return key;},validate(formId,role,permissions){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input`);input.setCustomValidity('');if(!Object.values(permissions).some(p=>p)){input.setCustomValidity('No permissions selected');}
if(this.permissions.some(p=>p.role===role)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();}}));Alpine.data('permissionsRow',()=>({role:'',read:false,create:false,update:false,xdelete:false,reset(){this.role='';this.read=this.create=this.update=this.xdelete=false;}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
throw new Error("This callback is only valid for forms");};},alert:function(text,classname){return function(alerts){alerts.add({text:text,class:classname||"success"},6000);};},redirect:function(url){return function(router){if(url==="/console"){window.location=url;return;}
router.change(url||"/");};},reload:function(){return function(router){router.reload();};},state:function(keys){let updateQueryString=function(key,value,url){var re=new RegExp("([?&])"+key+"=.*?(&|#|$)(.*)","gi"),hash;if(re.test(url)){if(typeof value!=="undefined"&&value!==null){return url.replace(re,"$1"+key+"="+value+"$2$3");}else{hash=url.split("#");url=hash[0].replace(re,"$1$3").replace(/(&|\?)$/,"");if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}
return url;}}else{if(typeof value!=="undefined"&&value!==null){var separator=url.indexOf("?")!==-1?"&":"?";hash=url.split("#");url=hash[0]+separator+key+"="+value;if(typeof hash[1]!=="undefined"&&hash[1]!==null){url+="#"+hash[1];}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -34,7 +34,7 @@
});
},
addPermission(formId, role, permissions) {
if (!document.getElementById(formId).reportValidity()) {
if (!this.validate(formId, role, permissions)) {
return;
}
Object.entries(permissions).forEach(entry => {
@ -105,6 +105,21 @@
return 'delete';
}
return key;
},
validate(formId, role, permissions) {
const form = document.getElementById(formId);
const input = document.getElementById(`${formId}Input`);
input.setCustomValidity('');
if (!Object.values(permissions).some(p => p)) {
input.setCustomValidity('No permissions selected');
}
if (this.permissions.some(p => p.role === role)) {
input.setCustomValidity('Role entry already exists');
}
return form.reportValidity();
}
}));
Alpine.data('permissionsRow', () => ({

View file

@ -7,8 +7,7 @@
return null;
}
return new Intl.DateTimeFormat('en-US', {
timeZone: 'UTC',
return new Intl.DateTimeFormat(navigator.languages, {
hourCycle: 'h24',
...format
}).format(new Date(datetime));

View file

@ -89,6 +89,10 @@
max-width: 800px;
}
&.width-xlarge {
max-width: 950px;
}
&.open {
display: block;
}

View file

@ -1,13 +1,12 @@
.permissions-matrix {
th:first-child, td:first-child {
width: 100px;
width: 35%;
}
th:not(:first-child):not(:last-child), td:not(:first-child):not(:last-child) {
width: 50px;
text-align: center;
}
th:last-child, td:last-child {
width: 20px;
width: 10%;
}
td {
vertical-align: middle;

View file

@ -0,0 +1,200 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
class Etsy extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://api.etsy.com/v3/public';
/**
* @var string
*/
private string $version = '2022-07-14';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
"email_r",
"profile_r",
];
/**
* @var string
*/
private string $pkce = '';
/**
* @return string
*/
private function getPKCE(): string
{
if (empty($this->pkce)) {
$this->pkce = \bin2hex(\random_bytes(rand(43, 128)));
}
return $this->pkce;
}
/**
* @return string
*/
public function getName(): string
{
return 'etsy';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://www.etsy.com/oauth/connect/oauth/authorize?' . \http_build_query([
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'response_type' => 'code',
'state' => \json_encode($this->state),
'scope' => $this->scopes,
'code_challenge' => $this->getPKCE(),
'code_challenge_method' => 'S256',
]);
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
$headers,
\http_build_query([
'grant_type' => 'authorization_code',
'client_id' => $this->appID,
'redirect_uri' => $this->callback,
'code' => $code,
'code_verifier' => $this->getPKCE(),
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$headers = ['Content-Type: application/x-www-form-urlencoded'];
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
$headers,
\http_build_query([
'grant_type' => 'refresh_token',
'client_id' => $this->appID,
'refresh_token' => $refreshToken,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$components = explode('.', $accessToken);
return $components[0];
}
/**
* @param $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
return $this->getUser($accessToken)['primary_email'];
}
/**
* Check if the OAuth email is verified
*
* OAuth is only allowed if account has been verified through Etsy, itself.
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$email = $this->getUserEmail($accessToken);
return !empty($email);
}
/**
* @param $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
return $this->getUser($accessToken)['login_name'];
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (!empty($this->user)) {
return $this->user;
}
$headers = ['Authorization: Bearer ' . $accessToken];
$this->user = \json_decode($this->request(
'GET',
'https://api.etsy.com/v3/application/users/' . $this->getUserID($accessToken),
), true);
return $this->user;
}
}

View file

@ -304,7 +304,7 @@ class OpenAPI3 extends Format
case 'Utopia\Database\Validator\DatetimeValidator':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'datetime';
$node['schema']['x-example'] = '2022-06-15T13:45:30.496';
$node['schema']['x-example'] = Model::TYPE_DATETIME_EXAMPLE;
break;
case 'Appwrite\Network\Validator\Email':
$node['schema']['type'] = $validator->getType();

View file

@ -300,7 +300,7 @@ class Swagger2 extends Format
case 'Utopia\Database\Validator\DatetimeValidator':
$node['type'] = $validator->getType();
$node['format'] = 'datetime';
$node['x-example'] = '2022-06-15T13:45:30.496';
$node['x-example'] = Model::TYPE_DATETIME_EXAMPLE;
break;
case 'Appwrite\Network\Validator\Email':
$node['type'] = $validator->getType();

View file

@ -0,0 +1,153 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
class IndexedQueries extends Queries
{
/**
* @var Document[]
*/
protected $attributes = [];
/**
* @var Document[]
*/
protected $indexes = [];
/**
* Expression constructor
*
* This Queries Validator filters indexes for only available indexes
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param Base ...$validators
* @param bool $strict
*/
public function __construct($attributes = [], $indexes = [], Base ...$validators)
{
$this->attributes = $attributes;
$this->indexes[] = new Document([
'type' => Database::INDEX_UNIQUE,
'attributes' => ['$id']
]);
$this->indexes[] = new Document([
'type' => Database::INDEX_KEY,
'attributes' => ['$createdAt']
]);
$this->indexes[] = new Document([
'type' => Database::INDEX_KEY,
'attributes' => ['$updatedAt']
]);
foreach ($indexes ?? [] as $index) {
$this->indexes[] = $index;
}
parent::__construct(...$validators);
}
/**
* Check if indexed array $indexes matches $queries
*
* @param array $indexes
* @param array $queries
*
* @return bool
*/
protected function arrayMatch(array $indexes, array $queries): bool
{
// Check the count of indexes first for performance
if (count($queries) !== count($indexes)) {
return false;
}
// Sort them for comparison, the order is not important here anymore.
sort($indexes, SORT_STRING);
sort($queries, SORT_STRING);
// Only matching arrays will have equal diffs in both directions
if (array_diff_assoc($indexes, $queries) !== array_diff_assoc($queries, $indexes)) {
return false;
}
return true;
}
/**
* Is valid.
*
* Returns false if:
* 1. any query in $value is invalid based on $validator
* 2. there is no index with an exact match of the filters
* 3. there is no index with an exact match of the order attributes
*
* Otherwise, returns true.
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
if (!parent::isValid($value)) {
return false;
}
$queries = [];
foreach ($value as $query) {
if (!$query instanceof Query) {
$query = Query::parse($query);
}
$queries[] = $query;
}
$grouped = Query::groupByType($queries);
/** @var Query[] */ $filters = $grouped['filters'];
/** @var string[] */ $orderAttributes = $grouped['orderAttributes'];
// Check filter queries for exact index match
if (count($filters) > 0) {
$filtersByAttribute = [];
foreach ($filters as $filter) {
$filtersByAttribute[$filter->getAttribute()] = $filter->getMethod();
}
$found = null;
foreach ($this->indexes as $index) {
if ($this->arrayMatch($index->getAttribute('attributes'), array_keys($filtersByAttribute))) {
$found = $index;
}
}
if (!$found) {
$this->message = 'Index not found: ' . implode(",", array_keys($filtersByAttribute));
return false;
}
// search method requires fulltext index
if (in_array(Query::TYPE_SEARCH, array_values($filtersByAttribute)) && $found['type'] !== Database::INDEX_FULLTEXT) {
$this->message = 'Search method requires fulltext index: ' . implode(",", array_keys($filtersByAttribute));
return false;
}
}
// Check order attributes for exact index match
$validator = new OrderAttributes($this->attributes, $this->indexes, true);
if (count($orderAttributes) > 0 && !$validator->isValid($orderAttributes)) {
$this->message = $validator->getDescription();
return false;
}
return true;
}
}

View file

@ -2,28 +2,140 @@
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Document;
use Utopia\Database\Validator\Queries as ValidatorQueries;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Validator;
use Utopia\Database\Query;
class Queries extends ValidatorQueries
class Queries extends Validator
{
/**
* Expression constructor
*
* This Queries Validator that filters indexes for only available indexes
*
* @param QueryValidator $validator
* @param Document[] $attributes
* @param Document[] $indexes
* @param bool $strict
* @var string
*/
public function __construct($validator, $attributes = [], $indexes = [], $strict = true)
{
// Remove failed/stuck/processing indexes
$availableIndexes = \array_filter($indexes, function ($index) {
return $index->getAttribute('status') === 'available';
});
protected $message = 'Invalid queries';
parent::__construct($validator, $attributes, $availableIndexes, $strict);
/**
* @var Base[]
*/
protected $validators;
/**
* Queries constructor
*
* @param Base ...$validators a list of validators
*/
public function __construct(Base ...$validators)
{
$this->validators = $validators;
}
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return $this->message;
}
/**
* Is valid.
*
* Returns false if:
* 1. any query in $value is invalid based on $validator
*
* Otherwise, returns true.
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
foreach ($value as $query) {
if (!$query instanceof Query) {
try {
$query = Query::parse($query);
} catch (\Throwable $th) {
$this->message = 'Invalid query: ${query}';
return false;
}
}
$method = $query->getMethod();
$methodType = '';
switch ($method) {
case Query::TYPE_LIMIT:
$methodType = Base::METHOD_TYPE_LIMIT;
break;
case Query::TYPE_OFFSET:
$methodType = Base::METHOD_TYPE_OFFSET;
break;
case Query::TYPE_CURSORAFTER:
case Query::TYPE_CURSORBEFORE:
$methodType = Base::METHOD_TYPE_CURSOR;
break;
case Query::TYPE_ORDERASC:
case Query::TYPE_ORDERDESC:
$methodType = Base::METHOD_TYPE_ORDER;
break;
case Query::TYPE_EQUAL:
case Query::TYPE_NOTEQUAL:
case Query::TYPE_LESSER:
case Query::TYPE_LESSEREQUAL:
case Query::TYPE_GREATER:
case Query::TYPE_GREATEREQUAL:
case Query::TYPE_SEARCH:
$methodType = Base::METHOD_TYPE_FILTER;
break;
default:
break;
}
$methodIsValid = false;
foreach ($this->validators as $validator) {
if ($validator->getMethodType() !== $methodType) {
continue;
}
if (!$validator->isValid($query)) {
$this->message = 'Query not valid: ' . $validator->getDescription();
return false;
}
$methodIsValid = true;
}
if (!$methodIsValid) {
$this->message = 'Query method not valid: ' . $method;
return false;
}
}
return true;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return true;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_OBJECT;
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Order;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
class Base extends Queries
{
/**
* Expression constructor
*
* @param string $collection
* @param string[] $allowedAttributes
*/
public function __construct(string $collection, array $allowedAttributes)
{
$collection = Config::getParam('collections', [])[$collection];
// array for constant lookup time
$allowedAttributesLookup = [];
foreach ($allowedAttributes as $attribute) {
$allowedAttributesLookup[$attribute] = true;
}
$attributes = [];
foreach ($collection['attributes'] as $attribute) {
$key = $attribute['$id'];
if (!isset($allowedAttributesLookup[$key])) {
continue;
}
$attributes[] = new Document([
'key' => $key,
'type' => $attribute['type'],
'array' => $attribute['array'],
]);
}
$attributes[] = new Document([
'key' => '$id',
'type' => Database::VAR_STRING,
'array' => false,
]);
$attributes[] = new Document([
'key' => '$createdAt',
'type' => Database::VAR_DATETIME,
'array' => false,
]);
$attributes[] = new Document([
'key' => '$updatedAt',
'type' => Database::VAR_DATETIME,
'array' => false,
]);
$validators = [
new Limit(),
new Offset(),
new Cursor(),
new Filter($attributes),
new Order($attributes),
];
parent::__construct(...$validators);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Buckets extends Base
{
public const ALLOWED_ATTRIBUTES = [
'enabled',
'name',
'fileSecurity',
'maximumFileSize',
'encryption',
'antivirus'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('buckets', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Collections extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'enabled',
'documentSecurity'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('collections', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Databases extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('databases', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Deployments extends Base
{
public const ALLOWED_ATTRIBUTES = [
'entrypoint',
'size',
'buildId',
'activate',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('deployments', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\IndexedQueries;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Order;
use Utopia\Database\Database;
use Utopia\Database\Document;
class Documents extends IndexedQueries
{
/**
* Expression constructor
*
* @param Document[] $attributes
* @param Document[] $indexes
*/
public function __construct(array $attributes, array $indexes)
{
$attributes[] = new Document([
'key' => '$id',
'type' => Database::VAR_STRING,
'array' => false,
]);
$attributes[] = new Document([
'key' => '$createdAt',
'type' => Database::VAR_DATETIME,
'array' => false,
]);
$attributes[] = new Document([
'key' => '$updatedAt',
'type' => Database::VAR_DATETIME,
'array' => false,
]);
$validators = [
new Limit(),
new Offset(),
new Cursor(),
new Filter($attributes),
new Order($attributes),
];
parent::__construct($attributes, $indexes, ...$validators);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Executions extends Base
{
public const ALLOWED_ATTRIBUTES = [
'trigger',
'status',
'statusCode',
'time'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('executions', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Files extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'signature',
'mimeType',
'sizeOriginal',
'chunksTotal',
'chunksUploaded'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('files', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Functions extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'status',
'runtime',
'deployment',
'schedule',
'scheduleNext',
'schedulePrevious',
'timeout'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('functions', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Memberships extends Base
{
public const ALLOWED_ATTRIBUTES = [
'userId',
'teamId',
'invited',
'joined',
'confirm'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('memberships', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Teams extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'total'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('teams', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Users extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'email',
'phone',
'status',
'passwordUpdate',
'registration',
'emailVerification',
'phoneVerification'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('users', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Utopia\Validator;
use Utopia\Database\Query;
abstract class Base extends Validator
{
public const METHOD_TYPE_LIMIT = 'limit';
public const METHOD_TYPE_OFFSET = 'offset';
public const METHOD_TYPE_CURSOR = 'cursor';
public const METHOD_TYPE_ORDER = 'order';
public const METHOD_TYPE_FILTER = 'filter';
/**
* @var string
*/
protected $message = 'Invalid query';
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return $this->message;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_OBJECT;
}
/**
* Returns what type of query this Validator is for
*/
abstract public function getMethodType(): string;
}

View file

@ -0,0 +1,44 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
class Cursor extends Base
{
/**
* Is valid.
*
* Returns true if method is cursorBefore or cursorAfter and value is not null
*
* Otherwise, returns false
*
* @param Query $value
*
* @return bool
*/
public function isValid($query): bool
{
// Validate method
$method = $query->getMethod();
if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) {
$cursor = $query->getValue();
$validator = new UID();
if ($validator->isValid($cursor)) {
return true;
}
$this->message = 'Invalid cursor: ' . $validator->getDescription();
return false;
}
return false;
}
public function getMethodType(): string
{
return self::METHOD_TYPE_CURSOR;
}
}

View file

@ -0,0 +1,114 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Database;
use Utopia\Database\Query;
class Filter extends Base
{
/**
* @var string
*/
protected $message = 'Invalid query';
/**
* @var array
*/
protected $schema = [];
/**
* Query constructor
*
* @param int $maxValuesCount
*/
public function __construct(array $attributes = [], int $maxValuesCount = 100)
{
foreach ($attributes as $attribute) {
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
}
$this->maxValuesCount = $maxValuesCount;
}
protected function isValidAttribute($attribute): bool
{
// Search for attribute in schema
if (!isset($this->schema[$attribute])) {
$this->message = 'Attribute not found in schema: ' . $attribute;
return false;
}
return true;
}
protected function isValidAttributeAndValues(string $attribute, array $values): bool
{
if (!$this->isValidAttribute($attribute)) {
return false;
}
$attributeSchema = $this->schema[$attribute];
if (count($values) > $this->maxValuesCount) {
$this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute;
return false;
}
// Extract the type of desired attribute from collection $schema
$attributeType = $attributeSchema['type'];
foreach ($values as $value) {
$condition = match ($attributeType) {
Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING,
default => gettype($value) === $attributeType
};
if (!$condition) {
$this->message = 'Query type does not match expected: ' . $attributeType;
return false;
}
}
return true;
}
/**
* Is valid.
*
* Returns true if method is a filter method, attribute exists, and value matches attribute type
*
* Otherwise, returns false
*
* @param Query $value
*
* @return bool
*/
public function isValid($query): bool
{
// Validate method
$method = $query->getMethod();
$attribute = $query->getAttribute();
switch ($method) {
case Query::TYPE_EQUAL:
case Query::TYPE_NOTEQUAL:
case Query::TYPE_LESSER:
case Query::TYPE_LESSEREQUAL:
case Query::TYPE_GREATER:
case Query::TYPE_GREATEREQUAL:
case Query::TYPE_SEARCH:
$values = $query->getValues();
return $this->isValidAttributeAndValues($attribute, $values);
default:
return false;
}
}
public function getMethodType(): string
{
return self::METHOD_TYPE_FILTER;
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Validator\Range;
class Limit extends Base
{
protected int $maxLimit;
/**
* Query constructor
*
* @param int $maxLimit
*/
public function __construct(int $maxLimit = 100)
{
$this->maxLimit = $maxLimit;
}
protected function isValidLimit($limit): bool
{
$validator = new Range(0, $this->maxLimit);
if ($validator->isValid($limit)) {
return true;
}
$this->message = 'Invalid limit: ' . $validator->getDescription();
return false;
}
/**
* Is valid.
*
* Returns true if method is limit values are within range.
*
* @param Query $value
*
* @return bool
*/
public function isValid($query): bool
{
// Validate method
$method = $query->getMethod();
if ($method !== Query::TYPE_LIMIT) {
$this->message = 'Query method invalid: ' . $method;
return false;
}
$limit = $query->getValue();
return $this->isValidLimit($limit);
}
public function getMethodType(): string
{
return self::METHOD_TYPE_LIMIT;
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Validator\Range;
class Offset extends Base
{
protected int $maxOffset;
/**
* Query constructor
*
* @param int $maxOffset
*/
public function __construct(int $maxOffset = 5000)
{
$this->maxOffset = $maxOffset;
}
protected function isValidOffset($offset): bool
{
$validator = new Range(0, $this->maxOffset);
if ($validator->isValid($offset)) {
return true;
}
$this->message = 'Invalid offset: ' . $validator->getDescription();
return false;
}
/**
* Is valid.
*
* Returns true if method is offset and values are within range.
*
* @param Query $value
*
* @return bool
*/
public function isValid($query): bool
{
// Validate method
$method = $query->getMethod();
if ($method !== Query::TYPE_OFFSET) {
$this->message = 'Query method invalid: ' . $method;
return false;
}
$offset = $query->getValue();
return $this->isValidOffset($offset);
}
public function getMethodType(): string
{
return self::METHOD_TYPE_OFFSET;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Utopia\Database\Query;
use Utopia\Validator;
class Order extends Base
{
/**
* @var array
*/
protected $schema = [];
/**
* Query constructor
*
*/
public function __construct(array $attributes = [])
{
foreach ($attributes as $attribute) {
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
}
}
protected function isValidAttribute($attribute): bool
{
// Search for attribute in schema
if (!isset($this->schema[$attribute])) {
$this->message = 'Attribute not found in schema: ' . $attribute;
return false;
}
return true;
}
/**
* Is valid.
*
* Returns true if method is ORDER_ASC or ORDER_DESC and attributes are valid
*
* Otherwise, returns false
*
* @param Query $value
*
* @return bool
*/
public function isValid($query): bool
{
$method = $query->getMethod();
$attribute = $query->getAttribute();
if ($method === Query::TYPE_ORDERASC || $method === Query::TYPE_ORDERDESC) {
if ($attribute === '') {
return true;
}
return $this->isValidAttribute($attribute);
}
return false;
}
public function getMethodType(): string
{
return self::METHOD_TYPE_ORDER;
}
}

View file

@ -2,12 +2,15 @@
namespace Tests\E2E\Scopes;
use Appwrite\Tests\Retryable;
use Tests\E2E\Client;
use PHPUnit\Framework\TestCase;
use Utopia\Database\ID;
abstract class Scope extends TestCase
{
use Retryable;
/**
* @var Client
*/

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Account;
use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Utopia\Database\ID;
use Utopia\Database\DateTime;
@ -390,7 +391,7 @@ trait AccountBase
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'limit' => 1
'queries' => [ 'limit(1)' ],
]);
$this->assertEquals($responseLimit['headers']['status-code'], 200);
@ -407,7 +408,7 @@ trait AccountBase
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'offset' => 1
'queries' => [ 'offset(1)' ],
]);
$this->assertEquals($responseOffset['headers']['status-code'], 200);
@ -424,8 +425,7 @@ trait AccountBase
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'limit' => 1,
'offset' => 1
'queries' => [ 'limit(1)', 'offset(1)' ],
]);
$this->assertEquals($responseLimitOffset['headers']['status-code'], 200);
@ -519,6 +519,7 @@ trait AccountBase
/**
* @depends testUpdateAccountName
*/
#[Retry(count: 1)]
public function testUpdateAccountPassword($data): array
{
$email = $data['email'] ?? '';

View file

@ -614,7 +614,7 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => $newName
'search' => $newName,
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -628,7 +628,7 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => $id
'search' => $id,
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -654,7 +654,8 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => '"' . $email . '"'
'search' => '"' . $email . '"',
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -668,7 +669,7 @@ class AccountCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'search' => $id
'search' => $id,
]);
$this->assertEquals($response['headers']['status-code'], 200);

View file

@ -985,8 +985,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'queries' => [ 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1006,8 +1005,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['DESC'],
'queries' => [ 'orderDesc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1122,7 +1120,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['documents'][0]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['documents'][0]['$id'] . '")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1134,7 +1132,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['documents'][2]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['documents'][2]['$id'] . '")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1147,8 +1145,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'queries' => [ 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $base['headers']['status-code']);
@ -1161,9 +1158,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'cursor' => $base['body']['documents'][1]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['documents'][1]['$id'] . '")', 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1177,8 +1172,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['DESC'],
'queries' => [ 'orderDesc("releaseYear")' ],
]);
$this->assertEquals(200, $base['headers']['status-code']);
@ -1191,9 +1185,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['DESC'],
'cursor' => $base['body']['documents'][1]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['documents'][1]['$id'] . '")', 'orderDesc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1207,7 +1199,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'unknown'
'queries' => [ 'cursorAfter("unknown")' ],
]);
$this->assertEquals(400, $documents['headers']['status-code']);
@ -1239,8 +1231,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['documents'][2]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['documents'][2]['$id'] . '")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1252,8 +1243,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['documents'][0]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['documents'][0]['$id'] . '")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1266,8 +1256,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'queries' => [ 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $base['headers']['status-code']);
@ -1280,10 +1269,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'cursor' => $base['body']['documents'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['documents'][1]['$id'] . '")', 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1297,8 +1283,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['DESC'],
'queries' => [ 'orderDesc("releaseYear")' ],
]);
$this->assertEquals(200, $base['headers']['status-code']);
@ -1311,10 +1296,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['DESC'],
'cursor' => $base['body']['documents'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['documents'][1]['$id'] . '")', 'orderDesc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1334,9 +1316,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 1,
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'queries' => [ 'limit(1)', 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
@ -1347,10 +1327,7 @@ trait DatabasesBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 2,
'offset' => 1,
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
'queries' => [ 'limit(2)', 'offset(1)', 'orderAsc("releaseYear")' ],
]);
$this->assertEquals(200, $documents['headers']['status-code']);

View file

@ -50,15 +50,61 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals($test1['body']['$id'], $databases['body']['databases'][0]['$id']);
$this->assertEquals($test2['body']['$id'], $databases['body']['databases'][1]['$id']);
/**
* Test for Order
*/
$base = array_reverse($databases['body']['databases']);
$databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderType' => 'DESC'
'queries' => [ 'limit(1)' ],
]);
$this->assertEquals(200, $databases['headers']['status-code']);
$this->assertCount(1, $databases['body']['databases']);
$databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ],
]);
$this->assertEquals(200, $databases['headers']['status-code']);
$this->assertCount(1, $databases['body']['databases']);
$databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("name", ["Test 1", "Test 2"])' ],
]);
$this->assertEquals(200, $databases['headers']['status-code']);
$this->assertCount(2, $databases['body']['databases']);
$databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("name", "Test 2")' ],
]);
$this->assertEquals(200, $databases['headers']['status-code']);
$this->assertCount(1, $databases['body']['databases']);
$databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("$id", "first")' ],
]);
$this->assertEquals(200, $databases['headers']['status-code']);
$this->assertCount(1, $databases['body']['databases']);
/**
* Test for Order
*/
$databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'orderDesc("")' ],
]);
$this->assertEquals(2, $databases['body']['total']);
@ -77,7 +123,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['databases'][0]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['databases'][0]['$id'] . '")' ],
]);
$this->assertCount(1, $databases['body']['databases']);
@ -87,7 +133,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['databases'][1]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['databases'][1]['$id'] . '")' ],
]);
$this->assertCount(0, $databases['body']['databases']);
@ -105,8 +151,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['databases'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['databases'][1]['$id'] . '")' ],
]);
$this->assertCount(1, $databases['body']['databases']);
@ -116,8 +161,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['databases'][0]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['databases'][0]['$id'] . '")' ],
]);
$this->assertCount(0, $databases['body']['databases']);
@ -163,7 +207,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'unknown',
'queries' => [ 'cursorAfter("unknown")' ],
]);
$this->assertEquals(400, $response['headers']['status-code']);
@ -287,15 +331,56 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals($test1['body']['$id'], $collections['body']['collections'][0]['$id']);
$this->assertEquals($test2['body']['$id'], $collections['body']['collections'][1]['$id']);
/**
* Test for Order
*/
$base = array_reverse($collections['body']['collections']);
$collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderType' => 'DESC'
'queries' => [ 'limit(1)' ]
]);
$this->assertEquals(200, $collections['headers']['status-code']);
$this->assertCount(1, $collections['body']['collections']);
$collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals(200, $collections['headers']['status-code']);
$this->assertCount(1, $collections['body']['collections']);
$collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("enabled", true)' ]
]);
$this->assertEquals(200, $collections['headers']['status-code']);
$this->assertCount(2, $collections['body']['collections']);
$collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("enabled", false)' ]
]);
$this->assertEquals(200, $collections['headers']['status-code']);
$this->assertCount(0, $collections['body']['collections']);
/**
* Test for Order
*/
$collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'orderDesc("")' ],
]);
$this->assertEquals(2, $collections['body']['total']);
@ -314,7 +399,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['collections'][0]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['collections'][0]['$id'] . '")' ],
]);
$this->assertCount(1, $collections['body']['collections']);
@ -324,7 +409,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['collections'][1]['$id']
'queries' => [ 'cursorAfter("' . $base['body']['collections'][1]['$id'] . '")' ],
]);
$this->assertCount(0, $collections['body']['collections']);
@ -342,8 +427,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['collections'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['collections'][1]['$id'] . '")' ],
]);
$this->assertCount(1, $collections['body']['collections']);
@ -353,8 +437,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $base['body']['collections'][0]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['collections'][0]['$id'] . '")' ],
]);
$this->assertCount(0, $collections['body']['collections']);
@ -400,7 +483,7 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'unknown',
'queries' => [ 'cursorAfter("unknown")' ],
]);
$this->assertEquals(400, $response['headers']['status-code']);

View file

@ -31,7 +31,7 @@ class DatabasesPermissionsGuestTest extends Scope
$this->assertEquals('InvalidDocumentDatabase', $database['body']['name']);
$databaseId = $database['body']['$id'];
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
$publicMovies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
'collectionId' => ID::unique(),
'name' => 'Movies',
'permissions' => [
@ -40,12 +40,23 @@ class DatabasesPermissionsGuestTest extends Scope
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$privateMovies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
'collectionId' => ID::unique(),
'name' => 'Movies',
'permissions' => [],
'documentSecurity' => true,
]);
$collection = ['id' => $movies['body']['$id']];
$publicCollection = ['id' => $publicMovies['body']['$id']];
$privateCollection = ['id' => $privateMovies['body']['$id']];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection['id'] . '/attributes/string', $this->getServerHeader(), [
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollection['id'] . '/attributes/string', $this->getServerHeader(), [
'key' => 'title',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollection['id'] . '/attributes/string', $this->getServerHeader(), [
'key' => 'title',
'size' => 256,
'required' => true,
@ -53,13 +64,14 @@ class DatabasesPermissionsGuestTest extends Scope
sleep(2);
return ['collectionId' => $collection['id'], 'databaseId' => $databaseId];
return [
'databaseId' => $databaseId,
'publicCollectionId' => $publicCollection['id'],
'privateCollectionId' => $privateCollection['id'],
];
}
/**
* [string[] $permissions]
*/
public function readDocumentsProvider()
public function permissionsProvider(): array
{
return [
[[Permission::read(Role::any())]],
@ -72,14 +84,23 @@ class DatabasesPermissionsGuestTest extends Scope
}
/**
* @dataProvider readDocumentsProvider
* @dataProvider permissionsProvider
*/
public function testReadDocuments($permissions)
{
$data = $this->createCollection();
$collectionId = $data['collectionId'];
$publicCollectionId = $data['publicCollectionId'];
$privateCollectionId = $data['privateCollectionId'];
$databaseId = $data['databaseId'];
$response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', $this->getServerHeader(), [
$publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', $this->getServerHeader(), [
'documentId' => ID::unique(),
'data' => [
'title' => 'Lorem',
],
'permissions' => $permissions,
]);
$privateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [
'documentId' => ID::unique(),
'data' => [
'title' => 'Lorem',
@ -87,18 +108,118 @@ class DatabasesPermissionsGuestTest extends Scope
'permissions' => $permissions,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(201, $publicResponse['headers']['status-code']);
$this->assertEquals(201, $privateResponse['headers']['status-code']);
$roles = Authorization::getRoles();
Authorization::cleanRoles();
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
$publicDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$privateDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals(1, $documents['body']['total']);
$this->assertEquals($permissions, $documents['body']['documents'][0]['$permissions']);
$this->assertEquals(1, $publicDocuments['body']['total']);
$this->assertEquals($permissions, $publicDocuments['body']['documents'][0]['$permissions']);
if (\in_array(Permission::read(Role::any()), $permissions)) {
$this->assertEquals(1, $privateDocuments['body']['total']);
$this->assertEquals($permissions, $privateDocuments['body']['documents'][0]['$permissions']);
} else {
$this->assertEquals(0, $privateDocuments['body']['total']);
}
foreach ($roles as $role) {
Authorization::setRole($role);
}
}
public function testWriteDocument()
{
$data = $this->createCollection();
$publicCollectionId = $data['publicCollectionId'];
$privateCollectionId = $data['privateCollectionId'];
$databaseId = $data['databaseId'];
$roles = Authorization::getRoles();
Authorization::cleanRoles();
$publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'documentId' => ID::unique(),
'data' => [
'title' => 'Lorem',
]
]);
$publicDocumentId = $publicResponse['body']['$id'];
$this->assertEquals(201, $publicResponse['headers']['status-code']);
$privateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'documentId' => ID::unique(),
'data' => [
'title' => 'Lorem',
],
]);
$this->assertEquals(401, $privateResponse['headers']['status-code']);
// Create a document in private collection with API key so we can test that update and delete are also not allowed
$privateResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents', $this->getServerHeader(), [
'documentId' => ID::unique(),
'data' => [
'title' => 'Lorem',
],
]);
$this->assertEquals(201, $privateResponse['headers']['status-code']);
$privateDocumentId = $privateResponse['body']['$id'];
$publicDocument = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'data' => [
'title' => 'Thor: Ragnarok',
],
]);
$this->assertEquals(200, $publicDocument['headers']['status-code']);
$this->assertEquals('Thor: Ragnarok', $publicDocument['body']['title']);
$privateDocument = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'data' => [
'title' => 'Thor: Ragnarok',
],
]);
$this->assertEquals(401, $privateDocument['headers']['status-code']);
$publicDocument = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents/' . $publicDocumentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals(204, $publicDocument['headers']['status-code']);
$privateDocument = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $privateCollectionId . '/documents/' . $privateDocumentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals(401, $privateDocument['headers']['status-code']);
foreach ($roles as $role) {
Authorization::setRole($role);

View file

@ -231,7 +231,75 @@ class FunctionsCustomClientTest extends Scope
];
}
public function testCreateExecutionUnauthorized(): array
public function testCreateCustomExecutionGuest()
{
/**
* Test for SUCCESS
*/
$projectId = $this->getProject()['$id'];
$apikey = $this->getProject()['apiKey'];
$function = $this->client->call(Client::METHOD_POST, '/functions', [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'vars' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
'funcKey3' => 'funcValue3',
],
'timeout' => 10,
]);
$functionId = $function['body']['$id'] ?? '';
$this->assertEquals(201, $function['headers']['status-code']);
$folder = 'php-fn';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
]);
$deploymentId = $deployment['body']['$id'] ?? '';
// Wait for deployment to be built.
sleep(10);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Why do we have to do this?
$function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], []);
$this->assertEquals(200, $function['headers']['status-code']);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], [
'data' => 'foobar',
]);
$this->assertEquals(202, $execution['headers']['status-code']);
}
public function testCreateExecutionNoDeployment(): array
{
$function = $this->client->call(Client::METHOD_POST, '/functions', [
'content-type' => 'application/json',
@ -252,7 +320,7 @@ class FunctionsCustomClientTest extends Scope
'async' => true,
]);
$this->assertEquals(401, $execution['headers']['status-code']);
$this->assertEquals(404, $execution['headers']['status-code']);
return [];
}
@ -293,7 +361,51 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'cursor' => $base['body']['executions'][0]['$id']
'queries' => [ 'limit(1)' ]
]);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(1, $executions['body']['executions']);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(1, $executions['body']['executions']);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'queries' => [ 'equal("status", ["completed"])' ]
]);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(2, $executions['body']['executions']);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'queries' => [ 'equal("status", ["failed"])' ]
]);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(0, $executions['body']['executions']);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'queries' => [ 'cursorAfter("' . $base['body']['executions'][0]['$id'] . '")' ],
]);
$this->assertCount(1, $executions['body']['executions']);
@ -304,8 +416,7 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-project' => $projectId,
'x-appwrite-key' => $apikey,
], [
'cursor' => $base['body']['executions'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $base['body']['executions'][1]['$id'] . '")' ],
]);
// Cleanup : Delete function

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Functions;
use Appwrite\Tests\Retry;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
@ -96,6 +97,46 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $response['body']['functions']);
$this->assertEquals($response['body']['functions'][0]['name'], 'Test');
$response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'limit(0)' ]
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertCount(0, $response['body']['functions']);
$response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertCount(0, $response['body']['functions']);
$response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("status", "disabled")' ]
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertCount(1, $response['body']['functions']);
$response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("status", "enabled")' ]
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertCount(0, $response['body']['functions']);
$response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -158,7 +199,7 @@ class FunctionsCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $functions['body']['functions'][0]['$id']
'queries' => [ 'cursorAfter("' . $functions['body']['functions'][0]['$id'] . '")' ],
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -169,8 +210,7 @@ class FunctionsCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $functions['body']['functions'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'cursorBefore("' . $functions['body']['functions'][1]['$id'] . '")' ],
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -184,7 +224,7 @@ class FunctionsCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'unknown',
'queries' => [ 'cursorAfter("unknown")' ],
]);
$this->assertEquals($response['headers']['status-code'], 400);
@ -412,6 +452,46 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(2, $function['body']['deployments']);
$this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']);
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'limit(1)' ]
]);
$this->assertEquals($function['headers']['status-code'], 200);
$this->assertCount(1, $function['body']['deployments']);
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals($function['headers']['status-code'], 200);
$this->assertCount(1, $function['body']['deployments']);
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("entrypoint", "index.php")' ]
]);
$this->assertEquals($function['headers']['status-code'], 200);
$this->assertCount(2, $function['body']['deployments']);
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("entrypoint", "index.js")' ]
]);
$this->assertEquals($function['headers']['status-code'], 200);
$this->assertCount(0, $function['body']['deployments']);
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -548,6 +628,36 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $function['body']['executions']);
$this->assertEquals($function['body']['executions'][0]['$id'], $data['executionId']);
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'limit(0)' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(0, $response['body']['executions']);
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(0, $response['body']['executions']);
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("trigger", "http")' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['executions']);
/**
* Test search queries
*/
@ -1209,6 +1319,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(204, $response['headers']['status-code']);
}
#[Retry(count: 1)]
public function testCreateCustomRubyExecution()
{
$name = 'ruby-3.1';

View file

@ -259,6 +259,42 @@ trait StorageBase
$this->assertGreaterThan(0, $files['body']['total']);
$this->assertGreaterThan(0, count($files['body']['files']));
$files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'limit(0)' ]
]);
$this->assertEquals(200, $files['headers']['status-code']);
$this->assertEquals(0, count($files['body']['files']));
$files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals(200, $files['headers']['status-code']);
$this->assertEquals(0, count($files['body']['files']));
$files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("mimeType", "image/png")' ]
]);
$this->assertEquals(200, $files['headers']['status-code']);
$this->assertEquals(1, count($files['body']['files']));
$files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("mimeType", "image/jpeg")' ]
]);
$this->assertEquals(200, $files['headers']['status-code']);
$this->assertEquals(0, count($files['body']['files']));
/**
* Test for FAILURE unknown Bucket
*/

View file

@ -49,13 +49,10 @@ class StorageCustomClientTest extends Scope
$this->assertEquals(201, $bucket['headers']['status-code']);
$this->assertNotEmpty($bucketId);
$roles = Authorization::getRoles();
Authorization::cleanRoles();
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
], [
'fileId' => ID::unique(),
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
]);
@ -68,38 +65,47 @@ class StorageCustomClientTest extends Scope
$this->assertEquals('image/png', $file['body']['mimeType']);
$this->assertEquals(47218, $file['body']['sizeOriginal']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
$file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], [
'name' => 'permissions.png',
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals(204, $file['headers']['status-code']);
$this->assertEmpty($file['body']);
@ -173,6 +179,15 @@ class StorageCustomClientTest extends Scope
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'permissions.png',
]);
$this->assertEquals(200, $file['headers']['status-code']);
/**
* Test for FAILURE
*/
@ -289,6 +304,15 @@ class StorageCustomClientTest extends Scope
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'permissions.png',
]);
$this->assertEquals(200, $file['headers']['status-code']);
/**
* Test for FAILURE
*/
@ -297,7 +321,7 @@ class StorageCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals($file['headers']['status-code'], 401);
$this->assertEquals(401, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [
'content-type' => 'multipart/form-data',
@ -307,6 +331,15 @@ class StorageCustomClientTest extends Scope
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
]);
$this->client->call(CLient::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'name' => 'permissions.png',
]);
$this->assertEquals(401, $file['headers']['status-code']);
$this->assertEquals($file['headers']['status-code'], 401);
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
@ -583,35 +616,31 @@ class StorageCustomClientTest extends Scope
$this->assertEquals('image/png', $file1['body']['mimeType']);
$this->assertEquals(47218, $file1['body']['sizeOriginal']);
$roles = Authorization::getRoles();
Authorization::cleanRoles();
Authorization::setRole(Role::any()->toString());
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/preview', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', array_merge([
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(200, $file['headers']['status-code']);
@ -628,16 +657,12 @@ class StorageCustomClientTest extends Scope
$this->assertEquals(401, $file['headers']['status-code']);
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
$file = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]);
$this->assertEquals(401, $file['headers']['status-code']);
foreach ($roles as $role) {
Authorization::setRole($role);
}
}
public function testFileUsersPermissions(): void

View file

@ -98,7 +98,47 @@ class StorageCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $response['body']['buckets'][0]['$id'],
'queries' => [ 'limit(1)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['buckets']);
$response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['buckets']);
$response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("$id", "bucket1")' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['buckets']);
$response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("fileSecurity", true)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(2, $response['body']['buckets']);
$response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'cursorAfter("' . $response['body']['buckets'][0]['$id'] . '")' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);

View file

@ -19,7 +19,8 @@ trait TeamsBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'teamId' => ID::unique(),
'name' => 'Arsenal'
'name' => 'Arsenal',
'roles' => ['player'],
]);
$this->assertEquals(201, $response1['headers']['status-code']);
@ -128,25 +129,41 @@ trait TeamsBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 2,
'queries' => [ 'limit(2)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, $response['body']['total']);
$this->assertIsInt($response['body']['total']);
$this->assertCount(2, $response['body']['teams']);
$this->assertEquals(2, count($response['body']['teams']));
$response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'offset' => 1,
'queries' => [ 'offset(1)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, $response['body']['total']);
$this->assertIsInt($response['body']['total']);
$this->assertGreaterThan(2, $response['body']['teams']);
$this->assertGreaterThan(1, count($response['body']['teams']));
$response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'greaterThanEqual("total", 0)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(2, count($response['body']['teams']));
$response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("name", ["Arsenal", "Newcastle"])' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(2, count($response['body']['teams']));
$response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
'content-type' => 'application/json',
@ -191,7 +208,7 @@ trait TeamsBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 2,
'queries' => [ 'limit(2)' ],
]);
$this->assertEquals(200, $teams['headers']['status-code']);
@ -203,8 +220,7 @@ trait TeamsBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 1,
'cursor' => $teams['body']['teams'][0]['$id']
'queries' => [ 'limit(1)', 'cursorAfter("' . $teams['body']['teams'][0]['$id'] . '")' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
@ -217,9 +233,7 @@ trait TeamsBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 1,
'cursor' => $teams['body']['teams'][1]['$id'],
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => [ 'limit(1)', 'cursorBefore("' . $teams['body']['teams'][1]['$id'] . '")' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);
@ -235,7 +249,7 @@ trait TeamsBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'unknown'
'queries' => [ 'cursorAfter("unknown")' ],
]);
$this->assertEquals(400, $response['headers']['status-code']);

View file

@ -30,10 +30,51 @@ trait TeamsBaseClient
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
$membershipId = $response['body']['memberships'][0]['$id'];
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'limit(0)' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(0, $response['body']['memberships']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'offset(1)' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(0, $response['body']['memberships']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("confirm", true)' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['memberships']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("confirm", false)' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(0, $response['body']['memberships']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -47,7 +88,8 @@ trait TeamsBaseClient
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
@ -62,7 +104,8 @@ trait TeamsBaseClient
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
@ -209,7 +252,7 @@ trait TeamsBaseClient
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $memberships['body']['memberships'][0]['$id']
'queries' => [ 'cursorAfter("' . $memberships['body']['memberships'][0]['$id'] . '")' ]
]);
$this->assertEquals(200, $response['headers']['status-code']);

View file

@ -3,7 +3,6 @@
namespace Tests\E2E\Services\Users;
use Tests\E2E\Client;
use Utopia\Database\Database;
use Utopia\Database\ID;
trait UsersBase
@ -379,11 +378,145 @@ trait UsersBase
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$this->assertEquals($response['body']['users'][1]['$id'], 'user1');
$user1 = $response['body']['users'][1];
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => $response['body']['users'][0]['$id']
'queries' => ['equal("name", "' . $user1['name'] . '")']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$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'] . '")']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['email'], $user1['email']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("status", true)']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount($totalUsers, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$this->assertEquals($response['body']['users'][0]['status'], $user1['status']);
$this->assertEquals($response['body']['users'][1]['$id'], $user1['$id']);
$this->assertEquals($response['body']['users'][1]['status'], $user1['status']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("status", false)']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertEmpty($response['body']['users']);
$this->assertCount(0, $response['body']['users']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("passwordUpdate", "' . $user1['passwordUpdate'] . '")']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['passwordUpdate'], $user1['passwordUpdate']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("registration", "' . $user1['registration'] . '")']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['registration'], $user1['registration']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("emailVerification", false)']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount($totalUsers, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $data['userId']);
$this->assertEquals($response['body']['users'][0]['status'], $user1['status']);
$this->assertEquals($response['body']['users'][1]['$id'], $user1['$id']);
$this->assertEquals($response['body']['users'][1]['status'], $user1['status']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("emailVerification", true)']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertEmpty($response['body']['users']);
$this->assertCount(0, $response['body']['users']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("phoneVerification", false)']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertIsArray($response['body']['users']);
$this->assertCount($totalUsers, $response['body']['users']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("phoneVerification", true)']
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertEmpty($response['body']['users']);
$this->assertCount(0, $response['body']['users']);
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['cursorAfter("' . $data['userId'] . '")']
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -396,8 +529,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'user1',
'cursorDirection' => Database::CURSOR_BEFORE
'queries' => ['cursorBefore("user1")']
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -413,7 +545,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'Ronaldo'
'search' => "Ronaldo",
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
@ -425,7 +557,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'cristiano.ronaldo@manchester-united.co.uk'
'search' => "cristiano.ronaldo@manchester-united.co.uk",
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
@ -437,7 +569,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'cristiano.ronaldo'
'search' => "cristiano.ronaldo",
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
@ -449,7 +581,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'manchester'
'search' => "manchester",
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
@ -461,7 +593,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'united.co.uk'
'search' => "united.co.uk",
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -475,7 +607,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'man'
'search' => "man",
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -489,7 +621,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $data['userId']
'search' => $data['userId'],
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -505,7 +637,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'cursor' => 'unknown'
'queries' => ['cursorAfter("unknown")']
]);
$this->assertEquals(400, $response['headers']['status-code']);
@ -596,7 +728,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $newName
'search' => $newName,
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -609,7 +741,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $id
'search' => $id,
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -663,7 +795,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $newEmail
'search' => $newEmail,
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -676,7 +808,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $id
'search' => $id,
]);
$this->assertEquals($response['headers']['status-code'], 200);
@ -852,7 +984,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 1
'queries' => [ 'limit(1)' ],
]);
$this->assertEquals($logs['headers']['status-code'], 200);
@ -864,7 +996,7 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'offset' => 1
'queries' => [ 'offset(1)' ],
]);
$this->assertEquals($logs['headers']['status-code'], 200);
@ -875,14 +1007,79 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'offset' => 1,
'limit' => 1
'queries' => [ 'limit(1)', 'offset(1)' ],
]);
$this->assertEquals($logs['headers']['status-code'], 200);
$this->assertIsArray($logs['body']['logs']);
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
$this->assertIsNumeric($logs['body']['total']);
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['limit(-1)']
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['limit(101)']
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['offset(-1)']
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['offset(5001)']
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['equal("$id", "asdf")']
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['orderAsc("$id")']
]);
$this->assertEquals($response['headers']['status-code'], 400);
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['cursorAsc("$id")']
]);
$this->assertEquals($response['headers']['status-code'], 400);
}
/**

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Webhooks;
use Appwrite\Tests\Retry;
use CURLFile;
use Tests\E2E\Client;
use Utopia\Database\DateTime;
@ -304,6 +305,7 @@ trait WebhooksBase
/**
* @depends testCreateCollection
*/
#[Retry(count: 1)]
public function testDeleteDocument(array $data): array
{
$actorsId = $data['actorsId'];

View file

@ -0,0 +1,16 @@
<?php
namespace Appwrite\Tests;
/**
* Allows test methods to be retried if they fail.
*
* Requires that the test class extends {@see TestCase} and has trait {@see Retryable}.
*/
#[\Attribute(\Attribute::TARGET_METHOD)]
class Retry
{
public function __construct(protected int $count = 1)
{
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Appwrite\Tests;
use PHPUnit\Framework\TestCase;
/**
* Allows test methods annotated with {@see Retry} to be retried.
*/
trait Retryable
{
/**
* Custom runBare, hides and defers to PHPUnit {@see TestCase} runBare function,
* accounting for any retries configured by the {@see Retry} annotation.
*
* @return void
* @throws \ReflectionException
* @throws \Throwable
*/
public function runBare(): void
{
$retries = $this->getNumberOfRetries();
$ex = null;
for ($i = 0; $i <= $retries; ++$i) {
try {
parent::runBare();
return;
} catch (\Throwable | \Exception $ex) {
// Swallow the exception until we have exhausted our retries.
if ($i !== $retries) {
echo 'Flaky test failed, retrying...' . PHP_EOL;
}
}
}
if ($ex) {
throw $ex;
}
}
/**
* @return int
* @throws \ReflectionException
*/
private function getNumberOfRetries(): int
{
$root = new \ReflectionClass($this);
$case = $this->getTestCaseRoot($root);
$name = $case->getProperty('name');
$name->setAccessible(true);
$name = $name->getValue($this);
$method = $root->getMethod($name);
$attributes = $method->getAttributes(Retry::class);
$attribute = $attributes[0] ?? null;
$args = $attribute?->getArguments();
$retries = $args['count'] ?? 0;
return \max(0, $retries);
}
/**
* @param \ReflectionClass $reflection
* @return \ReflectionClass
*/
private function getTestCaseRoot(\ReflectionClass $reflection): \ReflectionClass
{
if ($reflection->getName() === TestCase::class) {
return $reflection;
}
return $this->getTestCaseRoot($reflection->getParentClass());
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\IndexedQueries;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Order;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Database;
use Utopia\Database\Document;
class IndexedQueriesTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testEmptyQueries(): void
{
$validator = new IndexedQueries();
$this->assertEquals(true, $validator->isValid([]));
}
public function testInvalidQuery(): void
{
$validator = new IndexedQueries();
$this->assertEquals(false, $validator->isValid(["this.is.invalid"]));
}
public function testInvalidMethod(): void
{
$validator = new IndexedQueries();
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
$validator = new IndexedQueries([], [], new Limit());
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
}
public function testInvalidValue(): void
{
$validator = new IndexedQueries([], [], new Limit());
$this->assertEquals(false, $validator->isValid(['limit(-1)']));
}
public function testValid(): void
{
$attributes = [
new Document([
'key' => 'name',
'type' => Database::VAR_STRING,
'array' => false,
]),
];
$indexes = [
new Document([
'status' => 'available',
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
]),
new Document([
'status' => 'available',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
]),
];
$validator = new IndexedQueries(
$attributes,
$indexes,
new Cursor(),
new Filter($attributes),
new Limit(),
new Offset(),
new Order($attributes),
);
$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(['search("name", "value")']), $validator->getDescription());
}
public function testMissingIndex(): void
{
$attributes = [
new Document([
'key' => 'name',
'type' => Database::VAR_STRING,
'array' => false,
]),
];
$indexes = [
new Document([
'status' => 'available',
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
]),
];
$validator = new IndexedQueries(
$attributes,
$indexes,
new Cursor(),
new Filter($attributes),
new Limit(),
new Offset(),
new Order($attributes),
);
$this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription());
$this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription());
$this->assertEquals(false, $validator->isValid(['search("name", "value")']), $validator->getDescription());
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Base;
use PHPUnit\Framework\TestCase;
class CollectionTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testEmptyQueries(): void
{
$validator = new Base('users', []);
$this->assertEquals($validator->isValid([]), true);
}
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());
}
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());
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use PHPUnit\Framework\TestCase;
class UsersTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testIsValid(): void
{
$validator = new Users();
/**
* 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());
/**
* Test for Failure
*/
$this->assertEquals(false, $validator->isValid(['equal("password", "value")']), $validator->getDescription());
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Appwrite\Utopia\Database\Validator\Query\Order;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Database;
use Utopia\Database\Document;
class QueriesTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testEmptyQueries(): void
{
$validator = new Queries();
$this->assertEquals(true, $validator->isValid([]));
}
public function testInvalidQuery(): void
{
$validator = new Queries();
$this->assertEquals(false, $validator->isValid(["this.is.invalid"]));
}
public function testInvalidMethod(): void
{
$validator = new Queries();
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
$validator = new Queries(new Limit());
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
}
public function testInvalidValue(): void
{
$validator = new Queries(new Limit());
$this->assertEquals(false, $validator->isValid(['limit(-1)']));
}
public function testValid(): void
{
$attributes = [
new Document([
'key' => 'name',
'type' => Database::VAR_STRING,
'array' => false,
])
];
$validator = new Queries(
new Cursor(),
new Filter($attributes),
new Limit(),
new Offset(),
new Order($attributes),
);
$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());
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class CursorTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->validator = new Cursor();
}
public function tearDown(): void
{
}
public function testValue(): void
{
// Test for Success
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription());
// Test for Failure
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription());
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class FilterTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->validator = new Filter(
attributes: [
new Document([
'key' => 'attr',
'type' => Database::VAR_STRING,
'array' => false,
]),
],
);
}
public function tearDown(): void
{
}
public function testValue(): void
{
// Test for Success
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription());
// Test for Failure
$this->assertEquals($this->validator->isValid(Query::limit(1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(0)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(0)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(5000)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $this->validator->getDescription());
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class LimitTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->validator = new Limit(100);
}
public function tearDown(): void
{
}
public function testValue(): void
{
// Test for Success
$this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription());
// Test for Failure
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class OffsetTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->validator = new Offset(5000);
}
public function tearDown(): void
{
}
public function testValue(): void
{
// Test for Success
$this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription());
// Test for Failure
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription());
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Tests\Unit\Utopia\Database\Validator\Query;
use Appwrite\Utopia\Database\Validator\Query\Base;
use Appwrite\Utopia\Database\Validator\Query\Order;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use PHPUnit\Framework\TestCase;
class OrderTest extends TestCase
{
/**
* @var Base
*/
protected $validator = null;
public function setUp(): void
{
$this->validator = new Order(
attributes: [
new Document([
'key' => 'attr',
'type' => Database::VAR_STRING,
'array' => false,
]),
],
);
}
public function tearDown(): void
{
}
public function testValue(): void
{
// Test for Success
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderAsc('')), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), true, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderDesc('')), true, $this->validator->getDescription());
// Test for Failure
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription());
$this->assertEquals($this->validator->isValid(Query::orderAsc('dne')), false, $this->validator->getDescription());
}
}