diff --git a/app/config/collections/common.php b/app/config/collections/common.php index f68400e226..866d7b6d5b 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1417,6 +1417,13 @@ return [ 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], + [ + '$id' => ID::custom('_key_roles'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['roles'], + 'lengths' => [128], + 'orders' => [], + ], ], ], diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index e77751c124..4461fcada6 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -1665,6 +1665,17 @@ return [ 'array' => false, 'filters' => ['encrypt'] ], + [ + '$id' => ID::custom('secret'), + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => false, + 'array' => false, + 'filters' => [], + ], [ '$id' => ID::custom('search'), 'type' => Database::VAR_STRING, diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 316fe13116..e0b4e18e99 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.6.1", + "version": "1.6.2", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -6859,7 +6859,7 @@ }, { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", "required": false, "schema": { "type": "array", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 54161c4262..53a8962172 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.6.1", + "version": "1.6.2", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -25889,7 +25889,7 @@ }, { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", "required": false, "schema": { "type": "array", @@ -27982,6 +27982,30 @@ "x-example": "" }, "in": "path" + }, + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + }, + { + "name": "search", + "description": "Search term to filter your list results. Max length: 256 chars.", + "required": false, + "schema": { + "type": "string", + "x-example": "", + "default": "" + }, + "in": "query" } ] } diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 3d32d3e978..7fd174b6e0 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.6.1", + "version": "1.6.2", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -18099,7 +18099,7 @@ }, { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", "required": false, "schema": { "type": "array", @@ -20153,6 +20153,30 @@ "x-example": "" }, "in": "path" + }, + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "in": "query" + }, + { + "name": "search", + "description": "Search term to filter your list results. Max length: 256 chars.", + "required": false, + "schema": { + "type": "string", + "x-example": "", + "default": "" + }, + "in": "query" } ] } diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 8960bfaa5c..2ea4847792 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.6.1", + "version": "1.6.2", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -7069,7 +7069,7 @@ }, { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", "required": false, "type": "array", "collectionFormat": "multi", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 8fc7e7daf3..6e6b1317ee 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.6.1", + "version": "1.6.2", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -26371,7 +26371,7 @@ }, { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", "required": false, "type": "array", "collectionFormat": "multi", @@ -28514,6 +28514,27 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + }, + { + "name": "search", + "description": "Search term to filter your list results. Max length: 256 chars.", + "required": false, + "type": "string", + "x-example": "", + "default": "", + "in": "query" } ] } diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 83757c94f4..7c65c89764 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.6.1", + "version": "1.6.2", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -18565,7 +18565,7 @@ }, { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", "required": false, "type": "array", "collectionFormat": "multi", @@ -20669,6 +20669,27 @@ "type": "string", "x-example": "", "in": "path" + }, + { + "name": "queries", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles", + "required": false, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "default": [], + "in": "query" + }, + { + "name": "search", + "description": "Search term to filter your list results. Max length: 256 chars.", + "required": false, + "type": "string", + "x-example": "", + "default": "", + "in": "query" } ] } diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 8680d8aad3..89b93fd51f 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2516,10 +2516,11 @@ App::post('/v1/functions/:functionId/variables') ->param('functionId', '', new UID(), 'Function unique ID.', false) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) + ->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true) ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') - ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForPlatform) { + ->action(function (string $functionId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2540,6 +2541,7 @@ App::post('/v1/functions/:functionId/variables') 'resourceType' => 'function', 'key' => $key, 'value' => $value, + 'secret' => $secret, 'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']), ]); diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index b267e8e51e..a3c1933d8c 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -17,6 +17,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DateTimeValidator; use Utopia\Database\Validator\UID; +use Utopia\Validator\Boolean; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -399,11 +400,12 @@ App::post('/v1/project/variables') )) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) + ->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true) ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') - ->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) { + ->action(function (string $key, string $value, bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) { $variableId = ID::unique(); $variable = new Document([ @@ -418,6 +420,7 @@ App::post('/v1/project/variables') 'resourceType' => 'project', 'key' => $key, 'value' => $value, + 'secret' => $secret, 'search' => implode(' ', [$variableId, $key, 'project']), ]); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4a551b7478..17096731fa 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -21,6 +21,7 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Identities; +use Appwrite\Utopia\Database\Validator\Queries\Memberships; use Appwrite\Utopia\Database\Validator\Queries\Targets; use Appwrite\Utopia\Database\Validator\Queries\Users; use Appwrite\Utopia\Request; @@ -799,9 +800,11 @@ App::get('/v1/users/:userId/memberships') ] )) ->param('userId', '', new UID(), 'User 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/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true) + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $userId, Response $response, Database $dbForProject) { + ->action(function (string $userId, array $queries, string $search, Response $response, Database $dbForProject) { $user = $dbForProject->getDocument('users', $userId); @@ -809,6 +812,19 @@ App::get('/v1/users/:userId/memberships') throw new Exception(Exception::USER_NOT_FOUND); } + try { + $queries = Query::parseQueries($queries); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } + + if (!empty($search)) { + $queries[] = Query::search('search', $search); + } + + // Set internal queries + $queries[] = Query::equal('userInternalId', [$user->getInternalId()]); + $memberships = array_map(function ($membership) use ($dbForProject, $user) { $team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId')); @@ -818,7 +834,7 @@ App::get('/v1/users/:userId/memberships') ->setAttribute('userEmail', $user->getAttribute('email')); return $membership; - }, $user->getAttribute('memberships', [])); + }, $dbForProject->find('memberships', $queries)); $response->dynamic(new Document([ 'memberships' => $memberships, diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 56016f1057..17e93f43f5 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -93,6 +93,7 @@ abstract class Migration '1.6.0' => 'V21', '1.6.1' => 'V21', '1.6.2' => 'V22', + '1.7.0' => 'V23', ]; /** diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php new file mode 100644 index 0000000000..dec7e8e9d3 --- /dev/null +++ b/src/Appwrite/Migration/Version/V23.php @@ -0,0 +1,69 @@ + null, + fn () => [] + ); + } + + Console::info('Migrating Collections'); + $this->migrateCollections(); + } + + /** + * Migrate Collections. + * + * @return void + * @throws Exception|Throwable + */ + private function migrateCollections(): void + { + $internalProjectId = $this->project->getInternalId(); + $collectionType = match ($internalProjectId) { + 'console' => 'console', + default => 'projects', + }; + + $collections = $this->collections[$collectionType]; + foreach ($collections as $collection) { + $id = $collection['$id']; + + Console::log("Migrating Collection \"{$id}\""); + + $this->projectDB->setNamespace("_$internalProjectId"); + + switch ($id) { + case 'memberships': + // Create roles index + try { + $this->createIndexFromCollection($this->projectDB, $id, '_key_roles'); + } catch (Throwable $th) { + Console::warning("'_key_roles' from {$id}: {$th->getMessage()}"); + } + break; + } + + usleep(50000); + } + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php b/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php index 5ff0098662..cef562ba2c 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php @@ -9,12 +9,12 @@ class Memberships extends Base 'teamId', 'invited', 'joined', - 'confirm' + 'confirm', + 'roles', ]; /** * Expression constructor - * */ public function __construct() { diff --git a/src/Appwrite/Utopia/Response/Model/Variable.php b/src/Appwrite/Utopia/Response/Model/Variable.php index 88fcd14ca1..22f76e44d4 100644 --- a/src/Appwrite/Utopia/Response/Model/Variable.php +++ b/src/Appwrite/Utopia/Response/Model/Variable.php @@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; +use Utopia\Database\Document; class Variable extends Model { @@ -41,6 +42,12 @@ class Variable extends Model 'default' => '', 'example' => 'myPa$$word1', ]) + ->addRule('secret', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Variable secret flag. Secret variables can only be updated or deleted, but never read.', + 'default' => false, + 'example' => false, + ]) ->addRule('resourceType', [ 'type' => self::TYPE_STRING, 'description' => 'Service to which the variable belongs. Possible values are "project", "function"', @@ -56,6 +63,21 @@ class Variable extends Model ; } + /** + * Filter + * + * @param Document $document + * @return Document + */ + public function filter(Document $document): Document + { + $secret = $document->getAttribute('secret'); + if ($secret === true) { + $document->setAttribute('value', null); + } + return $document; + } + /** * Get Name * diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index 3a02cbcba2..0708d40aab 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -118,6 +118,22 @@ class FunctionsConsoleClientTest extends Scope $variableId = $variable['body']['$id']; + // test for secret variable + $variable = $this->createVariable( + $data['functionId'], + [ + 'key' => 'APP_TEST_1', + 'value' => 'TESTINGVALUE_1', + 'secret' => true + ] + ); + + $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertEquals('APP_TEST_1', $variable['body']['key']); + $this->assertEmpty($variable['body']['value']); + + $secretVariableId = $variable['body']['$id']; + /** * Test for FAILURE */ @@ -157,7 +173,8 @@ class FunctionsConsoleClientTest extends Scope return array_merge( $data, [ - 'variableId' => $variableId + 'variableId' => $variableId, + 'secretVariableId' => $secretVariableId ] ); } @@ -177,10 +194,12 @@ class FunctionsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, sizeof($response['body']['variables'])); - $this->assertEquals(1, $response['body']['total']); + $this->assertEquals(2, sizeof($response['body']['variables'])); + $this->assertEquals(2, $response['body']['total']); $this->assertEquals("APP_TEST", $response['body']['variables'][0]['key']); $this->assertEquals("TESTINGVALUE", $response['body']['variables'][0]['value']); + $this->assertEquals("APP_TEST_1", $response['body']['variables'][1]['key']); + $this->assertEmpty($response['body']['variables'][1]['value']); /** * Test for FAILURE @@ -207,6 +226,15 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals("APP_TEST", $response['body']['key']); $this->assertEquals("TESTINGVALUE", $response['body']['value']); + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_1", $response['body']['key']); + $this->assertEmpty($response['body']['value']); + /** * Test for FAILURE */ @@ -251,6 +279,27 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals("APP_TEST_UPDATE", $variable['body']['key']); $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); + $response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE_1', + 'value' => 'TESTINGVALUEUPDATED_1' + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_1", $response['body']['key']); + $this->assertEmpty($response['body']['value']); + + $variable = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_1", $variable['body']['key']); + $this->assertEmpty($variable['body']['value']); + $response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -332,6 +381,13 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8e2ab03880..a0ada0b774 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3930,6 +3930,23 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(201, $variable['headers']['status-code']); $variableId = $variable['body']['$id']; + // test for secret variable + $variable = $this->client->call(Client::METHOD_POST, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_1', + 'value' => 'TESTINGVALUE_1', + 'secret' => true + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertEquals('APP_TEST_1', $variable['body']['key']); + $this->assertEmpty($variable['body']['value']); + + $secretVariableId = $variable['body']['$id']; + /** * Test for FAILURE */ @@ -3973,6 +3990,7 @@ class ProjectsConsoleClientTest extends Scope $data, [ 'variableId' => $variableId, + 'secretVariableId' => $secretVariableId ] ); } @@ -3993,10 +4011,12 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(1, $response['body']['variables']); - $this->assertEquals(1, $response['body']['total']); + $this->assertCount(2, $response['body']['variables']); + $this->assertEquals(2, $response['body']['total']); $this->assertEquals("APP_TEST", $response['body']['variables'][0]['key']); $this->assertEquals("TESTINGVALUE", $response['body']['variables'][0]['value']); + $this->assertEquals("APP_TEST_1", $response['body']['variables'][1]['key']); + $this->assertEmpty($response['body']['variables'][1]['value']); return $data; } @@ -4019,6 +4039,16 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals("APP_TEST", $response['body']['key']); $this->assertEquals("TESTINGVALUE", $response['body']['value']); + $response = $this->client->call(Client::METHOD_GET, '/project/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_1", $response['body']['key']); + $this->assertEmpty($response['body']['value']); + /** * Test for FAILURE */ @@ -4066,6 +4096,29 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals("APP_TEST_UPDATE", $variable['body']['key']); $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE_1', + 'value' => 'TESTINGVALUEUPDATED_1' + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_1", $response['body']['key']); + $this->assertEmpty($response['body']['value']); + + $variable = $this->client->call(Client::METHOD_GET, '/project/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_1", $variable['body']['key']); + $this->assertEmpty($variable['body']['value']); + $response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $data['projectId'], @@ -4073,8 +4126,9 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(1, $response['body']['variables']); + $this->assertCount(2, $response['body']['variables']); $this->assertEquals("APP_TEST_UPDATE", $response['body']['variables'][0]['key']); + $this->assertEquals("APP_TEST_UPDATE_1", $response['body']['variables'][1]['key']); /** * Test for FAILURE @@ -4153,6 +4207,14 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEquals(204, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_DELETE, '/project/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + $response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $data['projectId'], diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 04e0eb5bc3..00e999672f 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -801,6 +801,97 @@ trait UsersBase return $data; } + /** + * @depends testGetUser + */ + public function testListUserMemberships(array $data): array + { + /** + * Test for SUCCESS + */ + + // create a new team + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => 'unique()', + 'name' => 'Test Team', + ]); + + // create a new membership + $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => $data['userId'], + 'roles' => ['new-role'], + ]); + + // list the memberships + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']); + $this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role']); + $this->assertEquals($response['body']['total'], 1); + + // create another membership with a new role + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => 'unique()', + 'name' => 'Test Team 2', + ]); + + $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => $data['userId'], + 'roles' => ['new-role-2'], + ]); + + // list out memberships and query by role + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::contains('roles', ['new-role-2'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']); + $this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role-2']); + $this->assertEquals($response['body']['total'], 1); + + /** + * Test for FAILURE + */ + + // query using equal on array field + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('roles', ['new-role-2'])->toString() + ] + ]); + + $this->assertEquals($response['body']['code'], 400); + $this->assertEquals($response['body']['message'], 'Invalid `queries` param: Invalid query: Cannot query equal on attribute "roles" because it is an array.'); + $this->assertEquals($response['body']['type'], 'general_argument_invalid'); + + return $data; + } + /** * @depends testGetUser */